Repository: nicbarker/clay Branch: main Commit: 76ec3632d80c Files: 99 Total size: 1.3 MB Directory structure: gitextract_vqarsh70/ ├── .dockerignore ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── cmake-multi-platform.yml │ └── odin-bindings-update.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── bindings/ │ ├── cpp/ │ │ └── README.md │ ├── csharp/ │ │ └── README │ ├── rust/ │ │ └── README │ └── zig/ │ └── README ├── clay.h ├── cmake/ │ └── FindCairo.cmake ├── examples/ │ ├── GLES3-GLFW-video-demo/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── Makefile.emscripten │ │ ├── Makefile.macos │ │ ├── README.md │ │ └── main.c │ ├── GLES3-SDL2-sidebar-scrolling-container/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── Makefile.emscripten │ │ ├── Makefile.macos │ │ ├── README.md │ │ └── main.c │ ├── GLES3-SDL2-video-demo/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── Makefile.emscripten │ │ ├── Makefile.macos │ │ ├── README.md │ │ └── main.c │ ├── SDL2-video-demo/ │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── SDL3-simple-demo/ │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── cairo-pdf-rendering/ │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── clay-official-website/ │ │ ├── CMakeLists.txt │ │ ├── build/ │ │ │ └── clay/ │ │ │ ├── index.html │ │ │ └── index.wasm │ │ ├── build.sh │ │ ├── index.html │ │ └── main.c │ ├── cpp-project-example/ │ │ ├── CMakeLists.txt │ │ └── main.cpp │ ├── introducing-clay-video-demo/ │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── playdate-project-example/ │ │ ├── .gitignore │ │ ├── CmakeLists.txt │ │ ├── README.md │ │ ├── Source/ │ │ │ └── pdxinfo │ │ ├── clay-video-demo-playdate.c │ │ └── main.c │ ├── raylib-multi-context/ │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── raylib-sidebar-scrolling-container/ │ │ ├── CMakeLists.txt │ │ ├── main.c │ │ └── multi-compilation-unit.c │ ├── shared-layouts/ │ │ └── clay-video-demo.c │ ├── sokol-corner-radius/ │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── sokol-video-demo/ │ │ ├── CMakeLists.txt │ │ ├── main.c │ │ └── sokol.c │ ├── termbox2-demo/ │ │ ├── CMakeLists.txt │ │ ├── main.c │ │ └── readme.md │ ├── termbox2-image-demo/ │ │ ├── CMakeLists.txt │ │ ├── main.c │ │ └── readme.md │ ├── terminal-example/ │ │ ├── CMakeLists.txt │ │ └── main.c │ └── win32_gdi/ │ ├── CMakeLists.txt │ ├── build.ps1 │ └── main.c ├── renderers/ │ ├── GLES3/ │ │ ├── clay_renderer_gles3.h │ │ └── clay_renderer_gles3_loader_stb.c │ ├── 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 │ ├── termbox2/ │ │ ├── clay_renderer_termbox2.c │ │ └── image_character_masks.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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ cmake-build-debug/ cmake-build-release/ .DS_Store .idea/ build/ node_modules/ ================================================ FILE: .github/FUNDING.yml ================================================ github: [nicbarker] ================================================ FILE: .github/workflows/cmake-multi-platform.yml ================================================ name: CMake on multiple platforms on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build: runs-on: ${{ matrix.os }} strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. fail-fast: false # Set up a matrix to run the following 3 configurations: # 1. # 2. # 3. # # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: os: [ubuntu-latest, windows-latest] build_type: [Release] c_compiler: [gcc, clang, cl] include: - os: windows-latest c_compiler: cl cpp_compiler: cl - os: ubuntu-latest c_compiler: gcc cpp_compiler: g++ - os: ubuntu-latest c_compiler: clang cpp_compiler: clang++ exclude: - os: windows-latest c_compiler: gcc - os: windows-latest c_compiler: clang - os: ubuntu-latest c_compiler: cl steps: - uses: actions/checkout@v4 - name: Set reusable strings # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. id: strings shell: bash run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - name: Cache uses: actions/cache@v4.2.0 with: # A list of files, directories, and wildcard patterns to cache and restore path: "/home/runner/work/clay/clay/build/_deps" # An explicit key for restoring and saving the cache key: "_deps" - name: Install Dependencies if: runner.os == 'Linux' run: | DEBIAN_FRONTEND=noninteractive sudo apt-get update -y DEBIAN_FRONTEND=noninteractive sudo apt-get install -y git DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libwayland-dev DEBIAN_FRONTEND=noninteractive sudo apt-get install -y pkg-config DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libxkbcommon-dev DEBIAN_FRONTEND=noninteractive sudo apt-get install -y xorg-dev - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: > cmake -B ${{ steps.strings.outputs.build-output-dir }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -S ${{ github.workspace }} - name: Build # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} - name: Test working-directory: ${{ steps.strings.outputs.build-output-dir }} # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --build-config ${{ matrix.build_type }} ================================================ FILE: .github/workflows/odin-bindings-update.yml ================================================ name: Odin Bindings Update on: push: branches: [main] jobs: check_changes: runs-on: ubuntu-latest outputs: changed: ${{ steps.check_clay.outputs.changed }} steps: - name: Checkout repo uses: actions/checkout@v4 with: fetch-depth: 2 - name: Check if clay.h changed id: check_clay run: | if git diff --name-only HEAD^ HEAD | grep -Fx "clay.h"; then echo "changed=true" >> $GITHUB_OUTPUT else echo "changed=false" >> $GITHUB_OUTPUT fi build: needs: check_changes if: needs.check_changes.outputs.changed == 'true' runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest] steps: - name: Checkout repo uses: actions/checkout@v4 - name: Install clang (Linux) if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y clang - name: Build libs run: | mkdir -p build mkdir -p artifacts cp clay.h clay.c COMMON_FLAGS="-DCLAY_IMPLEMENTATION -fno-ident -frandom-seed=clay" if [[ "$(uname)" == "Linux" ]]; then mkdir -p artifacts/linux mkdir -p artifacts/windows mkdir -p artifacts/wasm echo "Building for Linux..." clang -c $COMMON_FLAGS -fPIC -ffreestanding -static -target x86_64-unknown-linux-gnu clay.c -o build/linux.o ar rD artifacts/linux/clay.a build/linux.o echo "Building for Windows..." clang -c $COMMON_FLAGS -ffreestanding -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib clay.c -o artifacts/windows/clay.lib echo "Building for WASM..." clang -c $COMMON_FLAGS -fPIC -target wasm32 -nostdlib -static clay.c -o artifacts/wasm/clay.o elif [[ "$(uname)" == "Darwin" ]]; then mkdir -p artifacts/macos mkdir -p artifacts/macos-arm64 echo "Building for macOS (x86_64)..." clang -c $COMMON_FLAGS -fPIC -target x86_64-apple-macos clay.c -o build/macos.o libtool -static -o artifacts/macos/clay.a build/macos.o echo "Building for macOS (ARM64)..." clang -c $COMMON_FLAGS -fPIC -target arm64-apple-macos clay.c -o build/macos-arm64.o libtool -static -o artifacts/macos-arm64/clay.a build/macos-arm64.o fi rm -f clay.c build/*.o - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: artifacts-${{ matrix.os }} path: artifacts/ commit: needs: build runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v4 - name: Move artifacts run: | cp -r artifacts-ubuntu-latest/* bindings/odin/clay-odin/ cp -r artifacts-macos-latest/* bindings/odin/clay-odin/ - name: Commit/Push changes run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add bindings/odin/clay-odin/ git commit -m "[bindings/odin] Update Odin bindings" git push ================================================ FILE: .gitignore ================================================ cmake-build-debug/ cmake-build-release/ .DS_Store .idea/ node_modules/ *.dSYM .vs/ ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(clay) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") option(CLAY_INCLUDE_ALL_EXAMPLES "Build all examples" ON) option(CLAY_INCLUDE_DEMOS "Build video demo and website" OFF) option(CLAY_INCLUDE_CPP_EXAMPLE "Build C++ example" OFF) option(CLAY_INCLUDE_RAYLIB_EXAMPLES "Build raylib examples" OFF) option(CLAY_INCLUDE_SDL2_EXAMPLES "Build SDL 2 examples" OFF) option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF) option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF) option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF) option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF) message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}") if(APPLE) enable_language(OBJC) endif() if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_CPP_EXAMPLE) add_subdirectory("examples/cpp-project-example") endif() if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_DEMOS) if(NOT MSVC) add_subdirectory("examples/clay-official-website") add_subdirectory("examples/terminal-example") endif() add_subdirectory("examples/introducing-clay-video-demo") endif () if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_RAYLIB_EXAMPLES) add_subdirectory("examples/raylib-multi-context") add_subdirectory("examples/raylib-sidebar-scrolling-container") endif () if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL2_EXAMPLES) add_subdirectory("examples/SDL2-video-demo") endif () if(NOT MSVC AND (CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL3_EXAMPLES)) add_subdirectory("examples/SDL3-simple-demo") endif() if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES) add_subdirectory("examples/sokol-video-demo") add_subdirectory("examples/sokol-corner-radius") endif() # Playdate example not included in ALL because users need to install the playdate SDK first which requires a license agreement if(CLAY_INCLUDE_PLAYDATE_EXAMPLES) add_subdirectory("examples/playdate-project-example") endif() if(WIN32) # Build only for Win or Wine if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_WIN32_GDI_EXAMPLES) add_subdirectory("examples/win32_gdi") endif() endif() # add_subdirectory("examples/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now #add_library(${PROJECT_NAME} INTERFACE) #target_include_directories(${PROJECT_NAME} INTERFACE .) ================================================ FILE: LICENSE.md ================================================ zlib/libpng license Copyright (c) 2024 Nic Barker This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ================================================ FILE: README.md ================================================ # Clay, A UI Layout Library **_Clay_** (short for **C Layout**) is a high performance 2D UI layout library. ### Major Features - Microsecond layout performance - Flex-box like layout model for complex, responsive layouts including text wrapping, scrolling containers and aspect ratio scaling - Single ~4k LOC **clay.h** file with **zero** dependencies (including no standard library) - Wasm support: compile with clang to a 15kb uncompressed **.wasm** file for use in the browser - Static arena based memory use with no malloc / free, and low total memory overhead (e.g. ~3.5mb for 8192 layout elements). - React-like nested declarative syntax - Renderer agnostic: outputs a sorted list of rendering primitives that can be easily composited in any 3D engine, and even compiled to HTML (examples provided) Take a look at the [clay website](https://nicbarker.com/clay) for an example of clay compiled to wasm and running in the browser, or others in the [examples directory](https://github.com/nicbarker/clay/tree/main/examples). You can also watch the [introduction video](https://youtu.be/DYWTw19_8r4) for an overview of the motivation behind Clay's development and a short demo of its usage. A screenshot of a code IDE with lots of visual and textual elements _An example GUI application built with clay_ ## Quick Start Download or clone clay.h and include it after defining `CLAY_IMPLEMENTATION` in one file. ```C // Must be defined in one file, _before_ #include "clay.h" #define CLAY_IMPLEMENTATION #include "../../clay.h" const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; void HandleClayErrors(Clay_ErrorData errorData) { // See the Clay_ErrorData struct for more information printf("%s", errorData.errorText.chars); switch(errorData.errorType) { // etc } } // Example measure text function static inline Clay_Dimensions MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData) { // Clay_TextElementConfig contains members such as fontId, fontSize, letterSpacing etc // Note: Clay_String->chars is not guaranteed to be null terminated return (Clay_Dimensions) { .width = text.length * config->fontSize, // <- this will only work for monospace fonts, see the renderers/ directory for more advanced text measurement .height = config->fontSize }; } // Layout config is just a struct that can be declared statically, or inline Clay_ElementDeclaration sidebarItemConfig = (Clay_ElementDeclaration) { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) } }, .backgroundColor = COLOR_ORANGE }; // Re-useable components are just normal functions void SidebarItemComponent() { CLAY(id, sidebarItemConfig) { // children go here... } } int main() { // Note: malloc is only used here as an example, any allocator that provides // a pointer to addressable memory of at least totalMemorySize will work uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); // Note: screenWidth and screenHeight will need to come from your environment, Clay doesn't handle window related tasks Clay_Initialize(arena, (Clay_Dimensions) { screenWidth, screenHeight }, (Clay_ErrorHandler) { HandleClayErrors }); while(renderLoop()) { // Will be different for each renderer / environment // Optional: Update internal layout dimensions to support resizing Clay_SetLayoutDimensions((Clay_Dimensions) { screenWidth, screenHeight }); // Optional: Update internal pointer position for handling mouseover / click / touch events - needed for scrolling & debug tools Clay_SetPointerState((Clay_Vector2) { mousePositionX, mousePositionY }, isMouseDown); // Optional: Update internal pointer position for handling mouseover / click / touch events - needed for scrolling and debug tools Clay_UpdateScrollContainers(true, (Clay_Vector2) { mouseWheelX, mouseWheelY }, deltaTime); // All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout Clay_BeginLayout(); // An example of laying out a UI with a fixed width sidebar and flexible width main content CLAY(CLAY_ID("OuterContainer"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }, .backgroundColor = {250,250,255,255} }) { CLAY(CLAY_ID("SideBar"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }, .backgroundColor = COLOR_LIGHT }) { CLAY(CLAY_ID("ProfilePictureOuter"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = COLOR_RED }) { CLAY(CLAY_ID("ProfilePicture"), {.layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture } }) {} CLAY_TEXT(CLAY_STRING("Clay - UI Library"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255, 255, 255, 255} })); } // Standard C code like loops etc work inside components for (int i = 0; i < 5; i++) { SidebarItemComponent(); } CLAY(CLAY_ID("MainContent"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) {} } } // All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout Clay_RenderCommandArray renderCommands = Clay_EndLayout(); // More comprehensive rendering examples can be found in the renderers/ directory for (int i = 0; i < renderCommands.length; i++) { Clay_RenderCommand *renderCommand = &renderCommands.internalArray[i]; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { DrawRectangle( renderCommand->boundingBox, renderCommand->renderData.rectangle.backgroundColor); } // ... Implement handling of other command types } } } } ``` The above example, rendered correctly will look something like the following: ![Clay Example](https://github.com/user-attachments/assets/1928c6d4-ada9-4a4c-a3d1-44fe9b23b3bd) In summary, the general order of steps is: 1. [Clay_SetLayoutDimensions(dimensions)](#clay_setlayoutdimensions) 2. [Clay_SetPointerState(pointerPosition, isPointerDown)](#clay_setpointerstate) 3. [Clay_UpdateScrollContainers(enableDragScrolling, scrollDelta, deltaTime)](#clay_updatescrollcontainers) 4. [Clay_BeginLayout()](#clay_beginlayout) 5. Declare your layout with the provided [Element Macros](#element-macros) 6. [Clay_EndLayout()](#clay_endlayout) 7. Render the results using the outputted [Clay_RenderCommandArray](#clay_rendercommandarray) For help starting out or to discuss clay, considering joining [the discord server.](https://discord.gg/b4FTWkxdvT) ## Summary - [High Level Documentation](#high-level-documentation) - [Building UI Hierarchies](#building-ui-hierarchies) - [Configuring Layout and Styling UI Elements](#configuring-layout-and-styling-ui-elements) - [Element IDs](#element-ids) - [Mouse, Touch and Pointer Interactions](#mouse-touch-and-pointer-interactions) - [Scrolling Elements](#scrolling-elements) - [Floating Elements](#floating-elements-absolute-positioning) - [Custom Elements](#laying-out-your-own-custom-elements) - [Retained Mode Rendering](#retained-mode-rendering) - [Visibility Culling](#visibility-culling) - [Preprocessor Directives](#preprocessor-directives) - [Bindings](#bindings-for-non-c) - [Debug Tools](#debug-tools) - [API](#api) - [Naming Conventions](#naming-conventions) - [Public Functions](#public-functions) - [Lifecycle](#lifecycle-for-public-functions) - [Clay_MinMemorySize](#clay_minmemorysize) - [Clay_CreateArenaWithCapacityAndMemory](#clay_createarenawithcapacityandmemory) - [Clay_SetMeasureTextFunction](#clay_setmeasuretextfunction) - [Clay_ResetMeasureTextCache](#clay_resetmeasuretextcache) - [Clay_SetMaxElementCount](#clay_setmaxelementcount) - [Clay_SetMaxMeasureTextCacheWordCount](#clay_setmaxmeasuretextcachewordcount) - [Clay_Initialize](#clay_initialize) - [Clay_GetCurrentContext](#clay_getcurrentcontext) - [Clay_SetCurrentContext](#clay_setcurrentcontext) - [Clay_SetLayoutDimensions](#clay_setlayoutdimensions) - [Clay_SetPointerState](#clay_setpointerstate) - [Clay_UpdateScrollContainers](#clay_updatescrollcontainers) - [Clay_BeginLayout](#clay_beginlayout) - [Clay_EndLayout](#clay_endlayout) - [Clay_Hovered](#clay_hovered) - [Clay_OnHover](#clay_onhover) - [Clay_PointerOver](#clay_pointerover) - [Clay_GetScrollContainerData](#clay_getscrollcontainerdata) - [Clay_GetElementData](#clay_getelementdata) - [Clay_GetElementId](#clay_getelementid) - [Element Macros](#element-macros) - [CLAY](#clay) - [CLAY_ID](#clay_id) - [CLAY_IDI](#clay_idi) - [Data Structures & Defs](#data-structures--definitions) - [Clay_String](#clay_string) - [Clay_ElementId](#clay_elementid) - [Clay_RenderCommandArray](#clay_rendercommandarray) - [Clay_RenderCommand](#clay_rendercommand) - [Clay_ScrollContainerData](#clay_scrollcontainerdata) - [Clay_ErrorHandler](#clay_errorhandler) - [Clay_ErrorData](#clay_errordata) ## High Level Documentation ### Building UI Hierarchies Clay UIs are built using the C macro `CLAY(id, { configuration })`. This macro creates a new empty element in the UI hierarchy, and supports modular customisation of layout, styling and functionality. The `CLAY()` macro can also be _nested_, similar to other declarative UI systems like HTML. Child elements are added by opening a block: `{}` after calling the `CLAY()` macro (exactly like you would with an `if` statement or `for` loop), and declaring child components inside the braces. ```C // Parent element with 8px of padding CLAY(CLAY_ID("parent"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) { // Child element 1 CLAY_TEXT(CLAY_STRING("Hello World"), CLAY_TEXT_CONFIG({ .fontSize = 16 })); // Child element 2 with red background CLAY((CLAY_ID("child"), { .backgroundColor = COLOR_RED }) { // etc } } ``` However, unlike HTML and other declarative DSLs, this macro is just C. As a result, you can use arbitrary C code such as loops, functions and conditions inside your layout declaration code: ```C // Re-usable "components" are just functions that declare more UI void ButtonComponent(Clay_String buttonText) { // Red box button with 8px of padding CLAY_AUTO_ID({ .layout = { .padding = CLAY_PADDING_ALL(8) }, .backgroundColor = COLOR_RED }) { CLAY_TEXT(buttonText, textConfig); } } // Parent element CLAY(CLAY_ID("parent"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { // Render a bunch of text elements for (int i = 0; i < textArray.length; i++) { CLAY_TEXT(textArray.elements[i], textConfig); } // Only render this element if we're on a mobile screen if (isMobileScreen) { CLAY(0) { // etc } } // Re-usable components ButtonComponent(CLAY_STRING("Click me!")); ButtonComponent(CLAY_STRING("No, click me!")); }); ``` ### Configuring Layout and Styling UI Elements The layout and style of clay elements is configured with the [Clay_ElementDeclaration](#clay_elementdeclaration) struct passed to the `CLAY()` macro. ```C CLAY(CLAY_ID("box"), { .layout = { .padding = { 8, 8, 8, 8 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { // Children are 8px inset into parent, and laid out top to bottom } ``` This macro isn't magic - all it's doing is wrapping the standard designated initializer syntax. e.g. `(Clay_ElementDeclaration) { .layout = { .padding = { .left = 8, .right = 8 } ...`. See the [Clay_ElementDeclaration](#clay_elementdeclaration) API for the full list of options. A `Clay_ElementDeclaration` struct can be defined in file scope or elsewhere, and reused. ```C // Define a style in the global / file scope Clay_ElementDeclaration reuseableStyle = (Clay_ElementDeclaration) { .layout = { .padding = { .left = 12 } }, .backgroundColor = { 120, 120, 120, 255 }, .cornerRadius = { 12, 12, 12, 12 } }; CLAY(CLAY_ID("box"), reuseableStyle) { // ... } ``` ### Element IDs The Clay macro by default accepts an ID as its first argument, which is usually provided by the [CLAY_ID()](#clay_id) convenience macro. Elements can also be created with auto generated IDs, by using the [CLAY_AUTO_ID()](#clay-auto-id) macro. ```C // Will always produce the same ID from the same input string CLAY(CLAY_ID("OuterContainer"), { ...configuration }) {} // Generates a unique ID that may not be the same between two layout calls CLAY_AUTO_ID({ ...configuration }) {} ``` Element IDs have two main use cases. Firstly, tagging an element with an ID allows you to query information about the element later, such as its [mouseover state](#clay_pointerover) or dimensions. Secondly, IDs are visually useful when attempting to read and modify UI code, as well as when using the built-in [debug tools](#debug-tools). To avoid having to construct dynamic strings at runtime to differentiate ids in loops, clay provides the [CLAY_IDI(string, index)](#clay_idi) macro to generate different ids from a single input string. Think of IDI as "**ID** + **I**ndex" ```C // This is the equivalent of calling CLAY_ID("Item0"), CLAY_ID("Item1") etc for (int index = 0; index < items.length; index++) { CLAY(CLAY_IDI("Item", index), { ..configuration }) {} } ``` This ID will be forwarded to the final `Clay_RenderCommandArray` for use in retained mode UIs. Using duplicate IDs may cause some functionality to misbehave (i.e. if you're trying to attach a floating container to a specific element with ID that is duplicated, it may not attach to the one you expect) ### Mouse, Touch and Pointer Interactions Clay provides several functions for handling mouse and pointer interactions. All pointer interactions depend on the function `void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown)` being called after each mouse position update and before any other clay functions. **During UI declaration** The function `bool Clay_Hovered()` can be called during element construction or in the body of an element, and returns `true` if the mouse / pointer is over the currently open element. ```C // An orange button that turns blue when hovered CLAY(CLAY_ID("Button"), { .backgroundColor = Clay_Hovered() ? COLOR_BLUE : COLOR_ORANGE }) { bool buttonHovered = Clay_Hovered(); CLAY_TEXT(buttonHovered ? CLAY_STRING("Hovered") : CLAY_STRING("Hover me!"), headerTextConfig); } ``` The function `void Clay_OnHover()` allows you to attach a function pointer to the currently open element, which will be called if the mouse / pointer is over the element. ```C void HandleButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData) { ButtonData *buttonData = (ButtonData *)userData; // Pointer state allows you to detect mouse down / hold / release if (pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { // Do some click handling NavigateTo(buttonData->link); } } ButtonData linkButton = (ButtonData) { .link = "https://github.com/nicbarker/clay" }; // HandleButtonInteraction will be called for each frame the mouse / pointer / touch is inside the button boundaries CLAY(CLAY_ID("Button"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) { Clay_OnHover(HandleButtonInteraction, &linkButton); CLAY_TEXT(CLAY_STRING("Button"), &headerTextConfig); } ``` **Before / After UI declaration** If you want to query mouse / pointer overlaps outside layout declarations, you can use the function `bool Clay_PointerOver(Clay_ElementId id)`, which takes an [element id](#element-ids) and returns a bool representing whether the current pointer position is within its bounding box. ```C // Reminder: Clay_SetPointerState must be called before functions that rely on pointer position otherwise it will have no effect Clay_Vector2 mousePosition = { x, y }; Clay_SetPointerState(mousePosition); // ... // If profile picture was clicked if (mouseButtonDown(0) && Clay_PointerOver(Clay_GetElementId("ProfilePicture"))) { // Handle profile picture clicked } ``` Note that the bounding box queried by `Clay_PointerOver` is from the last frame. This generally shouldn't make a difference except in the case of animations that move at high speed. If this is an issue for you, performing layout twice per frame with the same data will give you the correct interaction the second time. ### Scrolling Elements Elements are configured as scrollable with the `.clip` configuration. Clipping instructs the renderer to not draw any pixels outside the clipped element's boundaries, and by specifying the `.childOffset` field, the clipped element's contents can be shifted around to provide "scrolling" behaviour. You can either calculate scrolling yourself and simply provide the current offset each frame to `.childOffset`, or alternatively, Clay provides a built in mechanism for tracking and updating scroll container offsets, detailed below. To make scroll containers respond to mouse wheel and scroll events, two functions need to be called before `BeginLayout()`: ```C Clay_Vector2 mousePosition = { x, y }; // Reminder: Clay_SetPointerState must be called before Clay_UpdateScrollContainers otherwise it will have no effect Clay_SetPointerState(mousePosition); // Clay_UpdateScrollContainers needs to be called before Clay_BeginLayout for the position to avoid a 1 frame delay Clay_UpdateScrollContainers( true, // Enable drag scrolling scrollDelta, // Clay_Vector2 scrollwheel / trackpad scroll x and y delta this frame float deltaTime, // Time since last frame in seconds as a float e.g. 8ms is 0.008f ); // ... // Clay internally tracks the scroll containers offset, and Clay_GetScrollOffset returns the x,y offset of the currently open element CLAY(CLAY_ID("ScrollContainer"), { .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { // Scrolling contents } // .childOffset can be provided directly if you would prefer to manage scrolling outside of clay CLAY(CLAY_ID("ScrollContainer"), { .clip = { .vertical = true, .childOffset = myData.scrollContainer.offset } }) { // Scrolling contents } ``` More specific details can be found in the docs for [Clay_UpdateScrollContainers](#clay_updatescrollcontainers), [Clay_SetPointerState](#clay_setpointerstate), [Clay_ClipElementConfig](#clay_clipelementconfig) and [Clay_GetScrollOffset](#clay_getscrolloffset). ### Floating Elements ("Absolute" Positioning) All standard elements in clay are laid out on top of, and _within_ their parent, positioned according to their parent's layout rules, and affect the positioning and sizing of siblings. **"Floating"** is configured with the `CLAY_FLOATING()` macro. Floating elements don't affect the parent they are defined in, or the position of their siblings. They also have a **z-index**, and as a result can intersect and render over the top of other elements. A classic example use case for floating elements is tooltips and modals. ```C // The two text elements will be laid out top to bottom, and the floating container // will be attached to "Outer" CLAY(CLAY_ID("Outer"), { .layout = { .layoutDirection = TOP_TO_BOTTOM } }) { CLAY_TEXT(text, &headerTextConfig); CLAY(CLAY_ID("Tooltip"), { .floating = { .attachTo = CLAY_ATTACH_TO_PARENT } }) {} CLAY_TEXT(text, &headerTextConfig); } ``` More specific details can be found in the full [Floating API](#clay_floatingelementconfig). ### Laying Out Your Own Custom Elements Clay only supports a simple set of UI element primitives, such as rectangles, text and images. Clay provides a singular API for layout out custom elements: ```C #include "clay.h" typedef enum { CUSTOM_ELEMENT_TYPE_MODEL, CUSTOM_ELEMENT_TYPE_VIDEO } CustomElementType; // A rough example of how you could handle laying out 3d models in your UI typedef struct { CustomElementType type; union { Model model; Video video; // ... }; } CustomElementData; Model myModel = Load3DModel(filePath); CustomElement modelElement = (CustomElement) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel } typedef struct { void* memory; uintptr_t offset; } Arena; // During init Arena frameArena = (Arena) { .memory = malloc(1024) }; // Custom elements only take a single pointer, so we need to store the data somewhere CustomElementData *modelData = (CustomElementData *)(frameArena.memory + frameArena.offset); *modelData = (CustomElementData) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel }; frameArena.offset += sizeof(CustomElementData); CLAY(CLAY_ID("3DModelViewer"), { .custom = { .customData = modelData } }) {} // Later during your rendering switch (renderCommand->commandType) { // ... case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { // Your extended struct is passed through CustomElementData *customElement = renderCommand->config.customElementConfig->customData; if (!customElement) continue; switch (customElement->type) { case CUSTOM_ELEMENT_TYPE_MODEL: { // Render your 3d model here break; } case CUSTOM_ELEMENT_TYPE_VIDEO: { // Render your video here break; } // ... } break; } } ``` More specific details can be found in the full [Custom Element API](#clay_customelementconfig). ### Retained Mode Rendering Clay was originally designed for [Immediate Mode](https://www.youtube.com/watch?v=Z1qyvQsjK5Y) rendering - where the entire UI is redrawn every frame. This may not be possible with your platform, renderer design or performance constraints. There are some general techniques that can be used to integrate clay into a retained mode rendering system: - `Clay_RenderCommand` includes the `uint32_t id` that was used to declare the element. If unique ids are used, these can be mapped to persistent graphics objects across multiple frames / layouts. - Render commands are culled automatically to only currently visible elements, and `Clay_RenderCommand` is a small enough struct that you can simply compare the memory of two render commands with matching IDs to determine if the element is "dirty" and needs to be re-rendered or updated. For a worked example, see the provided [HTML renderer](https://github.com/nicbarker/clay/blob/main/renderers/web/html/clay-html-renderer.html). This renderer converts clay layouts into persistent HTML documents with minimal changes per frame. ### Visibility Culling Clay provides a built-in visibility-culling mechanism that is **enabled by default**. It will only output render commands for elements that are visible - that is, **at least one pixel of their bounding box is inside the viewport.** This culling mechanism can be disabled via the use of the `#define CLAY_DISABLE_CULLING` directive. See [Preprocessor Directives](#preprocessor-directives) for more information. ### Preprocessor Directives Clay supports C preprocessor directives to modulate functionality at compile time. These can be set either in code using `#define CLAY_DISABLE_CULLING` or on the command line when compiling using the appropriate compiler specific arguments, e.g. `clang -DCLAY_DISABLE_CULLING main.c ...` The supported directives are: - `CLAY_WASM` - Required when targeting Web Assembly. - `CLAY_DLL` - Required when creating a .Dll file. ### Bindings for non C Clay is usable out of the box as a `.h` include in both C99 and C++20 with designated initializer support. There are also supported bindings for other languages, including: - [Odin Bindings](https://github.com/nicbarker/clay/tree/main/bindings/odin) - [Rust Bindings](https://github.com/clay-ui-rs/clay) ### Other implementations Clay has also been implemented in other languages: - [`glay`](https://github.com/soypat/glay) - Go line-by-line rewrite with readability as main goal. - [`totallygamerjet/clay`](https://github.com/totallygamerjet/clay) - Port using `cxgo`, a C to Go transpiler. - [`goclay`](https://github.com/igadmg/goclay) - Go line-by-line rewrite closely matching the reference. ### Debug Tools Clay includes built-in UI debugging tools, similar to the "inspector" in browsers such as Chrome or Firefox. These tools are included in `clay.h`, and work by injecting additional render commands into the output [Clay_RenderCommandArray](#clay_rendercommandarray). As long as the renderer that you're using works correctly, no additional setup or configuration is required to use the debug tools. To enable the debug tools, use the function `Clay_SetDebugModeEnabled(bool enabled)`. This boolean is persistent and does not need to be set every frame. The debug tooling by default will render as a panel to the right side of the screen, compressing your layout by its width. The default width is 400 and is currently configurable via the direct mutation of the internal variable `Clay__debugViewWidth`, however this is an internal API and is potentially subject to change. Screenshot 2024-09-12 at 12 54 03 PM _The official Clay website with debug tooling visible_ ### Running more than one Clay instance Clay allows you to run more than one instance in a program. To do this, [Clay_Initialize](#clay_initialize) returns a [Clay_Context*](#clay_context) reference. You can activate a specific instance using [Clay_SetCurrentContext](#clay_setcurrentcontext). If [Clay_SetCurrentContext](#clay_setcurrentcontext) is not called, then Clay will default to using the context from the most recently called [Clay_Initialize](#clay_initialize). **⚠ Important: Do not render instances across different threads simultaneously, as Clay does not currently support proper multi-threading.** ```c++ // Define separate arenas for the instances. Clay_Arena arena1, arena2; // ... allocate arenas // Initialize both instances, storing the context for each one. Clay_Context* instance1 = Clay_Initialize(arena1, layoutDimensions, errorHandler); Clay_Context* instance2 = Clay_Initialize(arena2, layoutDimensions, errorHandler); // In the program's render function, activate each instance before executing clay commands and macros. Clay_SetCurrentContext(instance1); Clay_BeginLayout(); // ... declare layout for instance1 Clay_RenderCommandArray renderCommands1 = Clay_EndLayout(); render(renderCommands1); // Switch to the second instance Clay_SetCurrentContext(instance2); Clay_BeginLayout(); // ... declare layout for instance2 Clay_RenderCommandArray renderCommands2 = Clay_EndLayout(); render(renderCommands2); ``` # API ### Naming Conventions - "**CAPITAL_LETTERS()**" are used for macros. - "**Clay__**" ("Clay" followed by **double** underscore) is used for internal functions that are not intended for use and are subject to change. - "**Clay_**" ("Clay" followed by **single** underscore) is used for external functions that can be called by the user. ## Public Functions ### Lifecycle for public functions **At startup / initialization time, run once** `Clay_MinMemorySize` -> `Clay_CreateArenaWithCapacityAndMemory` -> `Clay_Initialize` -> `Clay_SetMeasureTextFunction` **Each Frame** `Clay_SetLayoutDimensions` -> `Clay_SetPointerState` -> `Clay_UpdateScrollContainers` -> `Clay_BeginLayout` -> `CLAY() etc...` -> `Clay_EndLayout` --- ### Clay_MinMemorySize `uint32_t Clay_MinMemorySize()` Returns the minimum amount of memory **in bytes** that clay needs to accommodate the current [CLAY_MAX_ELEMENT_COUNT](#preprocessor-directives). --- ### Clay_CreateArenaWithCapacityAndMemory `Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *offset)` Creates a `Clay_Arena` struct with the given capacity and base memory pointer, which can be passed to [Clay_Initialize](#clay_initialize). --- ### Clay_SetMeasureTextFunction `void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData), uintptr_t userData)` Takes a pointer to a function that can be used to measure the `width, height` dimensions of a string. Used by clay during layout to determine [CLAY_TEXT](#clay_text) element sizing and wrapping. **Note 1: This string is not guaranteed to be null terminated.** Clay saves significant performance overhead by using slices when wrapping text instead of having to clone new null terminated strings. If your renderer does not support **ptr, length** style strings (e.g. Raylib), you will need to clone this to a new C string before rendering. **Note 2: It is essential that this function is as fast as possible.** For text heavy use-cases this function is called many times, and despite the fact that clay caches text measurements internally, it can easily become the dominant overall layout cost if the provided function is slow. **This is on the hot path!** --- ### Clay_ResetMeasureTextCache `void Clay_ResetMeasureTextCache(void)` Clay caches measurements from the provided MeasureTextFunction, and this will be sufficient for the majority of use-cases. However, if the measurements can depend on external factors that clay does not know about, like DPI changes, then the cached values may be incorrect. When one of these external factors changes, Clay_ResetMeasureTextCache can be called to force clay to recalculate all string measurements in the next frame. --- ### Clay_SetMaxElementCount `void Clay_SetMaxElementCount(uint32_t maxElementCount)` Sets the internal maximum element count that will be used in subsequent [Clay_Initialize()](#clay_initialize) and [Clay_MinMemorySize()](#clay_minmemorysize) calls, allowing clay to allocate larger UI hierarchies. **Note: You will need to reinitialize clay, after calling [Clay_MinMemorySize()](#clay_minmemorysize) to calculate updated memory requirements.** --- ### Clay_SetMaxMeasureTextCacheWordCount `void Clay_SetMaxMeasureTextCacheWordCount(uint32_t maxMeasureTextCacheWordCount)` Sets the internal text measurement cache size that will be used in subsequent [Clay_Initialize()](#clay_initialize) and [Clay_MinMemorySize()](#clay_minmemorysize) calls, allowing clay to allocate more text. The value represents how many separate words can be stored in the text measurement cache. **Note: You will need to reinitialize clay, after calling [Clay_MinMemorySize()](#clay_minmemorysize) to calculate updated memory requirements.** --- ### Clay_Initialize `Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler)` Initializes the internal memory mapping, sets the internal dimensions for layout, and binds an error handler for clay to use when something goes wrong. Returns a [Clay_Context*](#clay_context) that can optionally be given to [Clay_SetCurrentContext](#clay_setcurrentcontext) to allow running multiple instances of clay in the same program, and sets it as the current context. See [Running more than one Clay instance](#running-more-than-one-clay-instance). Reference: [Clay_Arena](#clay_createarenawithcapacityandmemory), [Clay_ErrorHandler](#clay_errorhandler), [Clay_SetCurrentContext](#clay_setcurrentcontext) --- ### Clay_SetCurrentContext `void Clay_SetCurrentContext(Clay_Context* context)` Sets the context that subsequent clay commands will operate on. You can get this reference from [Clay_Initialize](#clay_initialize) or [Clay_GetCurrentContext](#clay_getcurrentcontext). See [Running more than one Clay instance](#running-more-than-one-clay-instance). --- ### Clay_GetCurrentContext `Clay_Context* Clay_GetCurrentContext()` Returns the context that clay commands are currently operating on, or null if no context has been set. See [Running more than one Clay instance](#running-more-than-one-clay-instance). --- ### Clay_SetLayoutDimensions `void Clay_SetLayoutDimensions(Clay_Dimensions dimensions)` Sets the internal layout dimensions. Cheap enough to be called every frame with your screen dimensions to automatically respond to window resizing, etc. --- ### Clay_SetPointerState `void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown)` Sets the internal pointer position and state (i.e. current mouse / touch position) and recalculates overlap info, which is used for mouseover / click calculation (via [Clay_PointerOver](#clay_pointerover) and updating scroll containers with [Clay_UpdateScrollContainers](#clay_updatescrollcontainers). **isPointerDown should represent the current state this frame, e.g. it should be `true` for the entire duration the left mouse button is held down.** Clay has internal handling for detecting click / touch start & end. --- ### Clay_UpdateScrollContainers `void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime)` This function handles scrolling of containers. It responds to both `scrollDelta`, which represents mouse wheel or trackpad scrolling this frame, as well as "touch scrolling" on mobile devices, or "drag scrolling" with a mouse or similar device. Touch / drag scrolling only occurs if the `enableDragScrolling` parameter is `true`, **and** [Clay_SetPointerState](#clay_setpointerstate) has been called this frame. As a result, you can simply always call it with `false` as the first argument if you want to disable touch scrolling. `deltaTime` is the time **in seconds** since the last frame (e.g. 0.016 is **16 milliseconds**), and is used to normalize & smooth scrolling across different refresh rates. --- ### Clay_GetScrollOffset `Clay_Vector2 Clay_GetScrollOffset()` Returns the internally stored scroll offset for the currently open element. Generally intended for use with [clip elements](#clay_clipelementconfig) and the `.childOffset` field to create scrolling containers. See [Scrolling Elements](#scrolling-elements) for more details. ```C // Create a horizontally scrolling container CLAY(CLAY_ID("ScrollContainer"), { .clip = { .horizontal = true, .childOffset = Clay_GetScrollOffset() } }) ``` --- ### Clay_BeginLayout `void Clay_BeginLayout()` Prepares clay to calculate a new layout. Called each frame / layout **before** any of the [Element Macros](#element-macros). --- ### Clay_EndLayout `Clay_RenderCommandArray Clay_EndLayout()` Ends declaration of element macros and calculates the results of the current layout. Renders a [Clay_RenderCommandArray](#clay_rendercommandarray) containing the results of the layout calculation. --- ### Clay_Hovered `bool Clay_Hovered()` Called **during** layout declaration, and returns `true` if the pointer position previously set with `Clay_SetPointerState` is inside the bounding box of the currently open element. Note: this is based on the element's position from the **last** frame. --- ### Clay_OnHover `void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData), void *userData)` Called **during** layout declaration, this function allows you to attach a function pointer to the currently open element that will be called once per layout if the pointer position previously set with `Clay_SetPointerState` is inside the bounding box of the currently open element. See [Clay_PointerData](#clay_pointerdata) for more information on the `pointerData` argument. ```C void HandleButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData) { ButtonData *buttonData = (ButtonData *)userData; // Pointer state allows you to detect mouse down / hold / release if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { // Do some click handling NavigateTo(buttonData->link); } } ButtonData linkButton = (ButtonData) { .link = "https://github.com/nicbarker/clay" }; // HandleButtonInteraction will be called for each frame the mouse / pointer / touch is inside the button boundaries CLAY(CLAY_ID("Button"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) { Clay_OnHover(HandleButtonInteraction, &buttonData); CLAY_TEXT(CLAY_STRING("Click me!"), &headerTextConfig); } ``` --- ### Clay_PointerOver `bool Clay_PointerOver(Clay_ElementId id)` Returns `true` if the pointer position previously set with `Clay_SetPointerState` is inside the bounding box of the layout element with the provided `id`. Note: this is based on the element's position from the **last** frame. If frame-accurate pointer overlap detection is required, perhaps in the case of significant change in UI layout between frames, you can simply run your layout code twice that frame. The second call to `Clay_PointerOver` will be frame-accurate. ### Clay_GetScrollContainerData `Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id)` Returns [Clay_ScrollContainerData](#clay_scrollcontainerdata) for the scroll container matching the provided ID. This function allows imperative manipulation of scroll position, allowing you to build things such as scroll bars, buttons that "jump" to somewhere in a scroll container, etc. --- ### Clay_GetElementData `Clay_ElementData Clay_GetElementData(Clay_ElementId id)` Returns [Clay_ElementData](#clay_elementdata) for the element matching the provided ID. Used to retrieve information about elements such as their final calculated bounding box. --- ### Clay_GetElementId `Clay_ElementId Clay_GetElementId(Clay_String idString)` Returns a [Clay_ElementId](#clay_elementid) for the provided id string, used for querying element info such as mouseover state, scroll container data, etc. ## Element Macros ### CLAY() **Usage** `CLAY(...configuration) { ...children }` **Lifecycle** `Clay_BeginLayout()` -> `CLAY()` -> `Clay_EndLayout()` **Notes** **CLAY** opens a generic empty container, that is configurable and supports nested children. **CLAY** requires a parameter, so if you want to create an element without any configuration, use `CLAY(0)`. **Examples** ```C // Define an element with 16px of x and y padding CLAY(CLAY_ID("Outer"), { .layout = { .padding = CLAY_PADDING_ALL(16) } }) { // A nested child element CLAY(CLAY_ID("SideBar"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) { // Children laid out top to bottom with a 16 px gap between them } // A vertical scrolling container with a colored background CLAY(CLAY_ID("ScrollContainer"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 }, .backgroundColor = { 200, 200, 100, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(10), .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { // child elements } } ``` --- ### CLAY_AUTO_ID() A version of the core [CLAY()](#clay) element creation macro that generates an ID automatically instead of requiring it as the first argument. Note that under the hood this ID is generated in the same way as [CLAY_ID_LOCAL()](#clay_id_local), which is based on the element's position in the hierarchy, and may chance between layout calls if elements are added / removed from the hierarchy before the element is defined. As a result, for transitions & retained mode backends to work correctly, IDs should be specified. ```C // Note that CLAY_AUTO_ID only takes one argument: the configuration CLAY_AUTO_ID({ .layout = { .padding = CLAY_PADDING_ALL(16) } }) { // A nested child element CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) { // Children laid out top to bottom with a 16 px gap between them } // A vertical scrolling container with a colored background CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 }, .backgroundColor = { 200, 200, 100, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(10), .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { // child elements } } ``` --- ### CLAY_TEXT() **Usage** `CLAY_TEXT(Clay_String textContents, Clay_TextElementConfig *textConfig);` **Lifecycle** `Clay_BeginLayout()` -> `CLAY_TEXT()` -> `Clay_EndLayout()` **Notes** **TEXT** is a measured, auto-wrapped text element. It uses `Clay_TextElementConfig` to configure text specific options. Note that `Clay_TextElementConfig` uses `uint32_t fontId`. Font ID to font asset mapping is managed in user code and passed to render commands. **Struct API (Pseudocode)** ```C // CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .member = value })) supports these options Clay_TextElementConfig { Clay_Color textColor { float r; float g; float b; float a; }; uint16_t fontId; uint16_t fontSize; uint16_t letterSpacing; uint16_t lineHeight; Clay_TextElementConfigWrapMode wrapMode { CLAY_TEXT_WRAP_WORDS (default), CLAY_TEXT_WRAP_NEWLINES, CLAY_TEXT_WRAP_NONE, }; }; ``` **Fields** **`.textColor`** `CLAY_TEXT_CONFIG(.textColor = {120, 120, 120, 255})` Uses [Clay_Color](#clay_color). Conventionally accepts `rgba` float values between 0 and 255, but interpretation is left up to the renderer and does not affect layout. --- **`.fontId`** `CLAY_TEXT_CONFIG(.fontId = FONT_ID_LATO)` It's up to the user to load fonts and create a mapping from `fontId` to a font that can be measured and rendered. --- **`.fontSize`** `CLAY_TEXT_CONFIG(.fontSize = 16)` Font size is generally thought of as `x pixels tall`, but interpretation is left up to the user & renderer. --- **`.letterSpacing`** `CLAY_TEXT_CONFIG(.letterSpacing = 1)` `.letterSpacing` results in **horizontal** white space between individual rendered characters. --- **`.lineHeight`** `CLAY_TEXT_CONFIG(.lineHeight = 20)` `.lineHeight` - when non zero - forcibly sets the `height` of each wrapped line of text to `.lineheight` pixels tall. Will affect the layout of both parents and siblings. A value of `0` will use the measured height of the font. --- **`.wrapMode`** `CLAY_TEXT_CONFIG(.wrapMode = CLAY_TEXT_WRAP_NONE)` `.wrapMode` specifies under what conditions text should [wrap](https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap). Available options are: - `CLAY_TEXT_WRAP_WORDS` (default) - Text will wrap on whitespace characters as container width shrinks, preserving whole words. - `CLAY_TEXT_WRAP_NEWLINES` - will only wrap when encountering newline characters. - `CLAY_TEXT_WRAP_NONE` - Text will never wrap even if its container is compressed beyond the text measured width. --- **Examples** ```C // Define a font somewhere in your code const uint32_t FONT_ID_LATO = 3; // .. CLAY_TEXT(CLAY_STRING("John Smith"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_LATO, .fontSize = 24, .textColor = {255, 0, 0, 255} })); // Rendering example Font fontToUse = LoadedFonts[renderCommand->renderData.text->fontId]; ``` **Rendering** Element is subject to [culling](#visibility-culling). Otherwise, multiple `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_TEXT` may be created, one for each wrapped line of text. `Clay_RenderCommand.textContent` will be populated with a `Clay_String` _slice_ of the original string passed in (i.e. wrapping doesn't reallocate, it just returns a `Clay_String` pointing to the start of the new line with a `length`) --- ### CLAY_ID `Clay_ElementId CLAY_ID(STRING_LITERAL idString)` **CLAY_ID()** is used to generate and attach a [Clay_ElementId](#clay_elementid) to a layout element during declaration. Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SID](#clay_sid). To regenerate the same ID outside of layout declaration when using utility functions such as [Clay_PointerOver](#clay_pointerover), use the [Clay_GetElementId](#clay_getelementid) function. **Examples** ```C // Tag a button with the Id "Button" CLAY(CLAY_ID("Button"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 } }) { // ...children } // Later on outside of layout code bool buttonIsHovered = Clay_IsPointerOver(Clay_GetElementId("Button")); if (buttonIsHovered && leftMouseButtonPressed) { // ... do some click handling } ``` --- ### CLAY_SID() `Clay_ElementId CLAY_SID(Clay_String idString)` A version of [CLAY_ID](#clay_id) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame. --- ### CLAY_IDI() `Clay_ElementId CLAY_IDI(STRING_LITERAL idString, int32_t index)` An offset version of [CLAY_ID](#clay_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`. Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime. Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SIDI](#clay_sidi). --- ### CLAY_SIDI() `Clay_ElementId CLAY_SIDI(Clay_String idString, int32_t index)` A version of [CLAY_IDI](#clay_idi) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame. --- ### CLAY_ID_LOCAL() **Usage** `Clay_ElementId CLAY_ID_LOCAL(STRING_LITERAL idString)` **Lifecycle** `Clay_BeginLayout()` -> `CLAY(` -> `CLAY_ID_LOCAL()` -> `)` -> `Clay_EndLayout()` **Notes** **CLAY_ID_LOCAL()** is used to generate and attach a [Clay_ElementId](#clay_elementid) to a layout element during declaration. Unlike [CLAY_ID](#clay_id) which needs to be globally unique, a local ID is based on the ID of it's parent and only needs to be unique among its siblings. As a result, local id is suitable for use in reusable components and loops. Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SID_LOCAL](#clay_sid_local). **Examples** ```C void RenderHeaderButton(ButtonData button) { CLAY({ .id = CLAY_ID_LOCAL("HeaderButton"), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 } }) { // ...children } } for (int i = 0; i < headerButtons.length; i++) { RenderHeaderButton(headerButtons.items[i]); } ``` --- ### CLAY_SID_LOCAL() `Clay_ElementId CLAY_SID_LOCAL(Clay_String idString)` A version of [CLAY_ID_LOCAL](#clay_id_local) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame. --- ### CLAY_IDI_LOCAL() `Clay_ElementId CLAY_IDI_LOCAL(STRING_LITERAL idString, int32_t index)` An offset version of [CLAY_ID_LOCAL](#clay_local_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`. Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime. Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SIDI_LOCAL](#clay_sidi_local). --- ### CLAY_SIDI_LOCAL() `Clay_ElementId CLAY_SIDI_LOCAL(Clay_String idString, int32_t index)` A version of [CLAY_IDI_LOCAL](#clay_idi_local) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame. --- ## Data Structures & Definitions ### Clay_ElementDeclaration The **Clay_ElementDeclaration** struct is the only argument to the `CLAY()` macro and provides configuration options for layout elements. ```C typedef struct { Clay_ElementId id; Clay_LayoutConfig layout; Clay_Color backgroundColor; Clay_CornerRadius cornerRadius; Clay_AspectRatioElementConfig aspectRatio; Clay_ImageElementConfig image; Clay_FloatingElementConfig floating; Clay_CustomElementConfig custom; Clay_ClipElementConfig clip; Clay_BorderElementConfig border; void *userData; } Clay_ElementDeclaration; ``` **Fields** **`.layout`** - `Clay_LayoutConfig` `CLAY(CLAY_ID("Element"), { .layout = { .padding = { 16, 16, 12, 12 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } })` Uses [Clay_LayoutConfig](#clay_layoutconfig). Controls various settings related to _layout_, which can be thought of as "the size and position of this element and its children". --- **`.backgroundColor`** - `Clay_Color` `CLAY(CLAY_ID("Element"), { .backgroundColor = {120, 120, 120, 255} } })` Uses [Clay_Color](#clay_color). Conventionally accepts `rgba` float values between 0 and 255, but interpretation is left up to the renderer and does not affect layout. --- **`.cornerRadius`** - `float` `CLAY(CLAY_ID("Element"), { .cornerRadius = { .topLeft = 16, .topRight = 16, .bottomLeft = 16, .bottomRight = 16 } })` Defines the radius in pixels for the arc of rectangle corners (`0` is square, `rectangle.width / 2` is circular). Note that the `CLAY_CORNER_RADIUS(radius)` function-like macro is available to provide short hand for setting all four corner radii to the same value. e.g. `CLAY_BORDER({ .cornerRadius = CLAY_CORNER_RADIUS(10) })` --- **`.aspectRatio`** - `Clay_AspectRatioElementConfig` `CLAY(CLAY_ID("Element"), { .aspectRatio = 1 })` Uses [Clay_AspectRatioElementConfig](#clay_aspectratioelementconfig). Configures the element as an aspect ratio scaling element. Especially useful for rendering images, but can also be used to enforce a fixed width / height ratio of other elements. --- **`.image`** - `Clay_ImageElementConfig` `CLAY(CLAY_ID("Element"), { .image = { .imageData = &myImage } })` Uses [Clay_ImageElementConfig](#clay_imageelementconfig). Configures the element as an image element. Causes a render command with type `IMAGE` to be emitted. --- **`.floating`** - `Clay_FloatingElementConfig` `CLAY(CLAY_ID("Element"), { .floating = { .attachTo = CLAY_ATTACH_TO_PARENT } })` Uses [Clay_FloatingElementConfig](#clay_floatingelementconfig). Configures the element as an floating element, which allows it to stack "in front" and "on top" of other elements without affecting sibling or parent size or position. --- **`.custom`** - `Clay_CustomElementConfig` `CLAY(CLAY_ID("Element"), { .custom = { .customData = &my3DModel } })` Uses [Clay_CustomElementConfig](#clay_customelementconfig). Configures the element as a custom element, which allows you to pass custom data through to the renderer. Causes a render command with type `CUSTOM` to be emitted. --- **`.clip`** - `Clay_ClipElementConfig` `CLAY(CLAY_ID("Element"), { .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } })` Uses [Clay_ClipElementConfig](#clay_scrollelementconfig). Configures the element as a clip element, which causes child elements to be clipped / masked if they overflow, and together with the functions listed in [Scrolling Elements](#scrolling-elements) enables scrolling of child contents. An image demonstrating the concept of clipping which prevents rendering of a child elements pixels if they fall outside the bounds of the parent element. --- **`.border`** - `Clay_BorderElementConfig` `CLAY(CLAY_ID("Element"), { .border = { .width = { .left = 5 }, .color = COLOR_BLUE } })` Uses [Clay_BorderElementConfig](#clay_borderelementconfig). Configures the element as a border element, which instructs the renderer to draw coloured border lines along the perimeter of this element's bounding box. Causes a render command with type `BORDER` to be emitted. --- **`.userData`** - `void *` `CLAY(CLAY_ID("Element"), { .userData = &extraData })` Transparently passes a pointer through to the corresponding [Clay_RenderCommands](#clay_rendercommand)s generated by this element. --- **Examples** ```C // Declare a reusable rectangle config, with a purple color and 10px rounded corners Clay_RectangleElementConfig rectangleConfig = (Clay_RectangleElementConfig) { .color = { 200, 200, 100, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(10) }; // Declare a rectangle element using a reusable config CLAY(CLAY_ID("Box"), rectangleConfig) {} // Declare a retangle element using an inline config CLAY(CLAY_ID("BoxInline"), { .color = { 200, 200, 100, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(10) })) { // child elements } // Declare a scrolling container with a colored background CLAY(CLAY_ID("ScrollingContainer"), { .backgroundColor = { 200, 200, 100, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(10) .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } ) { // child elements } ``` Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE` will be created, with `renderCommand->elementConfig.rectangleElementConfig` containing a pointer to the element's Clay_RectangleElementConfig. ### Clay_LayoutConfig **Clay_LayoutConfig** is used for configuring _layout_ options (i.e. options that affect the final position and size of an element, its parents, siblings, and children) **Struct API (Pseudocode)** ```C // CLAY({ .layout = { ...fields } }) supports these options Clay_LayoutConfig { Clay_LayoutDirection layoutDirection = CLAY_LEFT_TO_RIGHT (default) | CLAY_TOP_TO_BOTTOM; Clay_Padding padding { u16 left; u16 right; u16 top; u16 bottom; }; uint16_t childGap; Clay_ChildAlignment childAlignment { .x = CLAY_ALIGN_X_LEFT (default) | CLAY_ALIGN_X_CENTER | CLAY_ALIGN_X_RIGHT; .y = CLAY_ALIGN_Y_TOP (default) | CLAY_ALIGN_Y_CENTER | CLAY_ALIGN_Y_BOTTOM; }; Clay_Sizing sizing { // Recommended to use the provided macros here - see #sizing for more in depth explanation .width = CLAY_SIZING_FIT(float min, float max) (default) | CLAY_SIZING_GROW(float min, float max) | CLAY_SIZING_FIXED(float width) | CLAY_SIZING_PERCENT(float percent) .height = CLAY_SIZING_FIT(float min, float max) (default) | CLAY_SIZING_GROW(float min, float max) | CLAY_SIZING_FIXED(float height) | CLAY_SIZING_PERCENT(float percent) }; // See CLAY_SIZING_GROW() etc for more details }; ``` **Fields** **`.layoutDirection`** - `Clay_LayoutDirection` `CLAY(CLAY_ID("Element"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } })` Controls the axis / direction in which child elements are laid out. Available options are `CLAY_LEFT_TO_RIGHT` (default) and `CLAY_TOP_TO_BOTTOM`. _Did you know that "left to right" and "top to bottom" both have 13 letters?_ Screenshot 2024-08-22 at 11 10 27 AM --- **`.padding`** - `Clay_Padding` `CLAY(CLAY_ID("Element"), { .layout = { .padding = { .left = 16, .right = 16, .top = 8, .bottom = 8 } } })` Controls white-space "padding" around the **outside** of child elements. Screenshot 2024-08-22 at 10 50 49 AM --- **`.childGap`** - `uint16_t` `CLAY(CLAY_ID("Element"), { .layout = { .childGap = 16 } })` Controls the white-space **between** child elements as they are laid out. When `.layoutDirection` is `CLAY_LEFT_TO_RIGHT` (default), this will be horizontal space, whereas for `CLAY_TOP_TO_BOTTOM` it will be vertical space. Screenshot 2024-08-22 at 11 05 15 AM --- **`.childAlignment`** - `Clay_ChildAlignment` `CLAY(CLAY_ID("Element"), { .layout = { .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER } } })` Controls the alignment of children relative to the height and width of the parent container. Available options are: ```C .x = CLAY_ALIGN_X_LEFT (default) | CLAY_ALIGN_X_CENTER | CLAY_ALIGN_X_RIGHT; .y = CLAY_ALIGN_Y_TOP (default) | CLAY_ALIGN_Y_CENTER | CLAY_ALIGN_Y_BOTTOM; ``` Screenshot 2024-08-22 at 11 25 16 AM --- **`.sizing`** - `Clay_Sizing` `CLAY(CLAY_ID("Element"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_PERCENT(0.5) } } })` Controls how final width and height of element are calculated. The same configurations are available for both the `.width` and `.height` axis. There are several options: - `CLAY_SIZING_FIT(float min, float max) (default)` - The element will be sized to fit its children (plus padding and gaps), up to `max`. If `max` is left unspecified, it will default to `FLOAT_MAX`. When elements are compressed to fit into a smaller parent, this element will not shrink below `min`. - `CLAY_SIZING_GROW(float min, float max)` - The element will grow to fill available space in its parent, up to `max`. If `max` is left unspecified, it will default to `FLOAT_MAX`. When elements are compressed to fit into a smaller parent, this element will not shrink below `min`. - `CLAY_SIZING_FIXED(float fixed)` - The final size will always be exactly the provided `fixed` value. Shorthand for `CLAY_SIZING_FIT(fixed, fixed)` - `CLAY_SIZING_PERCENT(float percent)` - Final size will be a percentage of parent size, minus padding and child gaps. `percent` is assumed to be a float between `0` and `1`. Screenshot 2024-08-22 at 2 10 33 PM Screenshot 2024-08-22 at 2 19 04 PM **Example Usage** ```C CLAY(CLAY_ID("Button"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16, .childGap = 16) } }) { // Children will be laid out vertically with 16px of padding around and between } ``` --- ### Clay_ImageElementConfig **Usage** `CLAY(CLAY_ID("Element"), { .image = { ...image config } }) {}` **Clay_ImageElementConfig** configures a clay element to render an image as its background. **Struct API (Pseudocode)** ```C Clay_ImageElementConfig { void * imageData; }; ``` **Fields** **`.imageData`** - `void *` `CLAY(CLAY_ID("Image"), { .image = { .imageData = &myImage } }) {}` `.imageData` is a generic void pointer that can be used to pass through image data to the renderer. ```C // Load an image somewhere in your code YourImage profilePicture = LoadYourImage("profilePicture.png"); // Note that when rendering, .imageData will be void* type. CLAY(CLAY_ID("Image"), { .image = { .imageData = &profilePicture } }) {} ``` **Examples** ```C // Load an image somewhere in your code YourImage profilePicture = LoadYourImage("profilePicture.png"); // Declare a reusable image config Clay_ImageElementConfig imageConfig = (Clay_ImageElementConfig) { .imageData = &profilePicture }; // Declare an image element using a reusable config CLAY(CLAY_ID("Image"), { .image = imageConfig }) {} // Declare an image element using an inline config CLAY(CLAY_ID("ImageInline"), { .image = { .imageData = &profilePicture }, .aspectRatio = 16.0 / 9.0 }) {} // Rendering example YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData; ``` **Rendering** Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE` will be created. The user will need to access `renderCommand->renderData.image->imageData` to retrieve image data referenced during layout creation. It's also up to the user to decide how / if they wish to blend `renderCommand->renderData.image->backgroundColor` with the image. --- ### Clay_AspectRatioElementConfig **Usage** `CLAY(CLAY_ID("Aspect"), { .aspectRatio = 16.0 / 9.0 }) {}` **Clay_AspectRatioElementConfig** configures a clay element to enforce a fixed width / height ratio in its final dimensions. Mostly used for image elements, but can also be used for non image elements. **Struct API (Pseudocode)** ```C Clay_AspectRatioElementConfig { float aspectRatio; }; ``` **Fields** **`.aspectRatio`** - `float` `CLAY(CLAY_ID("Aspect"), { .aspectRatio = { .aspectRatio = 16.0 / 9.0 } }) {}` or alternatively, as C will automatically pass the value to the first nested struct field: `CLAY(CLAY_ID("Aspect"), { .aspectRatio = 16.0 / 9.0 }) {}` **Examples** ```C // Load an image somewhere in your code YourImage profilePicture = LoadYourImage("profilePicture.png"); // Declare an image element that will grow along the X axis while maintaining its original aspect ratio CLAY(CLAY_ID("ProfilePicture"), { .layout = { .width = CLAY_SIZING_GROW() }, .aspectRatio = profilePicture.width / profilePicture.height, .image = { .imageData = &profilePicture }, }) {} ``` --- ### Clay_ImageElementConfig **Usage** `CLAY(CLAY_ID("Image"), { .image = { ...image config } }) {}` **Clay_ImageElementConfig** configures a clay element to render an image as its background. **Struct API (Pseudocode)** ```C Clay_ImageElementConfig { void * imageData; }; ``` **Fields** **`.imageData`** - `void *` `CLAY(CLAY_ID("Image"), { .image = { .imageData = &myImage } }) {}` `.imageData` is a generic void pointer that can be used to pass through image data to the renderer. ```C // Load an image somewhere in your code YourImage profilePicture = LoadYourImage("profilePicture.png"); // Note that when rendering, .imageData will be void* type. CLAY(CLAY_ID("Image"), { .image = { .imageData = &profilePicture } }) {} ``` Note: for an image to maintain its original aspect ratio when using dynamic scaling, the [.aspectRatio](#clay_aspectratioelementconfig) config option must be used. **Examples** ```C // Load an image somewhere in your code YourImage profilePicture = LoadYourImage("profilePicture.png"); // Declare a reusable image config Clay_ImageElementConfig imageConfig = (Clay_ImageElementConfig) { .imageData = &profilePicture }; // Declare an image element using a reusable config CLAY(CLAY_ID("Image"), { .image = imageConfig }) {} // Declare an image element using an inline config CLAY(CLAY_ID("ImageInline"), { .image = { .imageData = &profilePicture }, .aspectRatio = 16.0 / 9.0 }) {} // Rendering example YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData; ``` **Rendering** Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE` will be created. The user will need to access `renderCommand->renderData.image->imageData` to retrieve image data referenced during layout creation. It's also up to the user to decide how / if they wish to blend `renderCommand->renderData.image->backgroundColor` with the image. --- ### Clay_ClipElementConfig **Usage** `CLAY(CLAY_ID("ScrollBox"), { .clip = { ...clip config } }) {}` **Notes** `Clay_ClipElementConfig` configures the element as a clipping container, enabling masking of children that extend beyond its boundaries. Note: In order to process scrolling based on pointer position and mouse wheel or touch interactions, you must call `Clay_SetPointerState()` and `Clay_UpdateScrollContainers()` _before_ calling `BeginLayout`. **Struct Definition (Pseudocode)** ```C Clay_ClipElementConfig { bool horizontal; bool vertical; }; ``` **Fields** **`.horizontal`** - `bool` `CLAY(CLAY_ID("HorizontalScroll"), { .clip = { .horizontal = true } })` Enables or disables horizontal clipping for this container element. --- **`.vertical`** - `bool` `CLAY(LAY_ID("VerticalScroll"), { .clip = { .vertical = true } })` Enables or disables vertical clipping for this container element. --- **Rendering** Enabling clip for an element will result in two additional render commands: - `commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START`, which should create a rectangle mask with its `boundingBox` and is **not** subject to [culling](#visibility-culling) - `commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END`, which disables the previous rectangle mask and is **not** subject to [culling](#visibility-culling) **Examples** ```C CLAY(CLAY_ID("ScrollOuter"), { .clip = { .vertical = true } }) { // Create child content with a fixed height of 5000 CLAY(CLAY_ID("ScrollInner"), { .layout = { .sizing = { .height = CLAY_SIZING_FIXED(5000) } } }) {} } ``` --- ### Clay_BorderElementConfig **Usage** `CLAY(CLAY_ID("Border"), { .border = { ...border config } }) {}` **Notes** `Clay_BorderElementConfig` adds borders to the edges or between the children of elements. It uses Clay_BorderElementConfig to configure border specific options. **Struct Definition (Pseudocode)** ```C typedef struct Clay_BorderElementConfig { Clay_Color color { float r; float g; float b; float a; }; Clay_BorderWidth width { uint16_t left; uint16_t right; uint16_t top; uint16_t bottom; uint16_t betweenChildren; }; } Clay_BorderElementConfig; ``` **Fields** **`.color`** - `Clay_Color` `CLAY(CLAY_ID("Border"), { .border = { .color = { 255, 0, 0, 255 } } })` Uses [Clay_Color](#clay_color). Specifies the shared color for all borders configured by this element. Conventionally accepts `rgba` float values between 0 and 255, but interpretation is left up to the renderer and does not affect layout. --- **`.width`** - `Clay_BorderWidth` `CLAY(CLAY_ID("Border"), { .border = { .width = { .left = 2, .right = 10 } } })` Indicates to the renderer that a border of `.color` should be draw at the specified edges of the bounding box, **inset and overlapping the box contents by `.width`**. This means that border configuration does not affect layout, as the width of the border doesn't contribute to the total container width or layout position. Border containers with zero padding will be drawn over the top of child elements. Note: **`.width.betweenChildren`** `CLAY(CLAY_ID("Border"), { .border = { .width = { .betweenChildren = 2 } }, .color = COLOR_RED })` Configures the width and color of borders to be drawn between children. These borders will be vertical lines if the parent uses `.layoutDirection = CLAY_LEFT_TO_RIGHT` and horizontal lines if the parent uses `CLAY_TOP_TO_BOTTOM`. Unlike `.left, .top` etc, this option **will generate additional rectangle render commands representing the borders between children.** As a result, the renderer does not need to specifically implement rendering for these border elements. --- **Examples** ```C // 300x300 container with a 1px red border around all the edges CLAY(CLAY_ID("OuterBorder"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIXED(300) } }, .border = { .width = { 1, 1, 1, 1, 0 }, .color = COLOR_RED } }) { // ... } // Container with a 3px yellow bottom border CLAY(CLAY_ID("OuterBorder"), { .border = { .width = { .bottom = 3 }, .color = COLOR_YELLOW } }) { // ... } // Container with a 5px curved border around the edges, and a 5px blue border between all children laid out top to bottom CLAY(CLAY_ID("OuterBorder"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM }, .border = { .width = { 5, 5, 5, 5, 5 }, .color = COLOR_BLUE } }) { // Child // -- 5px blue border will be here -- // Child // -- 5px blue border will be here -- // Child } ``` **Rendering** Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand` with `commandType = CLAY_RENDER_COMMAND_TYPE_BORDER` representing the container will be created. Rendering of borders and rounded corners is left up to the user. See the provided [Raylib Renderer](https://github.com/nicbarker/clay/tree/main/renderers/raylib) for examples of how to draw borders using line and curve primitives. --- ### Clay_FloatingElementConfig **Usage** `CLAY(CLAY_ID("Floating"), { .floating = { ...floating config } }) {}` **Notes** **Floating Elements** defines an element that "floats" above other content. Typical use-cases include tooltips and modals. Floating containers: - With the default configuration, attach to the top left corner of their "parent" - Don't affect the width and height of their parent - Don't affect the positioning of sibling elements - Depending on their z-index can appear above or below other elements, partially or completely occluding them - Apart from positioning, function just like standard elements - including expanding to fit their children, etc. The easiest mental model to use when thinking about floating containers is that they are a completely separate UI hierarchy, attached to a specific x,y point on their "parent". Floating elements uses `Clay_FloatingElementConfig` to configure specific options. **Struct Definition (Pseudocode)** ```C Clay_FloatingElementConfig { Clay_Vector2 offset { float x, float y }; Clay_Dimensions expand { float width, float height }; uint32_t parentId; int16_t zIndex; Clay_FloatingAttachPoints attachPoint { .element = CLAY_ATTACH_POINT_LEFT_TOP (default) | CLAY_ATTACH_POINT_LEFT_CENTER | CLAY_ATTACH_POINT_LEFT_BOTTOM | CLAY_ATTACH_POINT_CENTER_TOP | CLAY_ATTACH_POINT_CENTER_CENTER | CLAY_ATTACH_POINT_CENTER_BOTTOM | CLAY_ATTACH_POINT_RIGHT_TOP | CLAY_ATTACH_POINT_RIGHT_CENTER | CLAY_ATTACH_POINT_RIGHT_BOTTOM .parent = CLAY_ATTACH_POINT_LEFT_TOP (default) | CLAY_ATTACH_POINT_LEFT_CENTER | CLAY_ATTACH_POINT_LEFT_BOTTOM | CLAY_ATTACH_POINT_CENTER_TOP | CLAY_ATTACH_POINT_CENTER_CENTER | CLAY_ATTACH_POINT_CENTER_BOTTOM | CLAY_ATTACH_POINT_RIGHT_TOP | CLAY_ATTACH_POINT_RIGHT_CENTER | CLAY_ATTACH_POINT_RIGHT_BOTTOM }; Clay_FloatingAttachToElement attachTo { CLAY_POINTER_CAPTURE_MODE_CAPTURE (default), CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH }; Clay_FloatingAttachToElement attachTo { CLAY_ATTACH_TO_NONE (default), CLAY_ATTACH_TO_PARENT, CLAY_ATTACH_TO_ELEMENT_WITH_ID, CLAY_ATTACH_TO_ROOT, }; }; ``` **Fields** **`.offset`** - `Clay_Vector2` `CLAY(CLAY_ID("Floating"), { .floating = { .offset = { -24, -24 } } })` Used to apply a position offset to the floating container _after_ all other layout has been calculated. --- **`.expand`** - `Clay_Dimensions` `CLAY(CLAY_ID("Floating"), { .floating = { .expand = { 16, 16 } } })` Used to expand the width and height of the floating container _before_ laying out child elements. --- **`.zIndex`** - `float` `CLAY(CLAY_ID("Floating"), { .floating = { .zIndex = 1 } })` All floating elements (as well as their entire child hierarchies) will be sorted by `.zIndex` order before being converted to render commands. If render commands are drawn in order, elements with higher `.zIndex` values will be drawn on top. --- **`.parentId`** - `uint32_t` `CLAY(CLAY_ID("Floating"), { .floating = { .parentId = Clay_GetElementId("HeaderButton").id } })` By default, floating containers will "attach" to the parent element that they are declared inside. However, there are cases where this limitation could cause significant performance or ergonomics problems. `.parentId` allows you to specify a `CLAY_ID().id` to attach the floating container to. The parent element with the matching id can be declared anywhere in the hierarchy, it doesn't need to be declared before or after the floating container in particular. Consider the following case: ```C // Load an image somewhere in your code CLAY(CLAY_IDI("SidebarButton", 1), { }) { // .. some button contents if (tooltip.attachedButtonIndex == 1) { CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ }) } } CLAY(CLAY_IDI("SidebarButton", 2), { }) { // .. some button contents if (tooltip.attachedButtonIndex == 2) { CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ }) } } CLAY(CLAY_IDI("SidebarButton", 3), { }) { // .. some button contents if (tooltip.attachedButtonIndex == 3) { CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ }) } } CLAY(CLAY_IDI("SidebarButton", 4), { }) { // .. some button contents if (tooltip.attachedButtonIndex == 4) { CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ }) } } CLAY(CLAY_IDI("SidebarButton", 5), { }) { // .. some button contents if (tooltip.attachedButtonIndex == 5) { CLAY(CLAY_ID("OptionTooltip"), { /* floating config... */ }) } } ``` The definition of the above UI is significantly polluted by the need to conditionally render floating tooltips as a child of many possible elements. The alternative, using `parentId`, looks like this: ```C // Load an image somewhere in your code CLAY(CLAY_IDI("SidebarButton", 1), { }) { // .. some button contents } CLAY(CLAY_IDI("SidebarButton", 2), { }) { // .. some button contents } CLAY(CLAY_IDI("SidebarButton", 3), { }) { // .. some button contents } CLAY(CLAY_IDI("SidebarButton", 4), { }) { // .. some button contents } CLAY(CLAY_IDI("SidebarButton", 5), { }) { // .. some button contents } // Any other point in the hierarchy CLAY(CLAY_ID("OptionTooltip"), { .floating = { .attachTo = CLAY_ATTACH_TO_ELEMENT_ID, .parentId = CLAY_IDI("SidebarButton", tooltip.attachedButtonIndex).id }) { // Tooltip contents... } ``` --- **`.attachment`** - `Clay_FloatingAttachPoints` `CLAY(CLAY_ID("Floating"), { .floating = { .attachment = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } }) {}` In terms of positioning the floating container, `.attachment` specifies - The point on the floating container (`.element`) - The point on the parent element that it "attaches" to (`.parent`) ![Screenshot 2024-08-23 at 11 47 21 AM](https://github.com/user-attachments/assets/b8c6dfaa-c1b1-41a4-be55-013473e4a6ce) You can mentally visualise this as finding a point on the floating container, then finding a point on the parent, and lining them up over the top of one another. For example: "Attach the LEFT_CENTER of the floating container to the RIGHT_TOP of the parent" `CLAY(CLAY_ID("Floating"), { .floating = { .attachment = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } });` ![Screenshot 2024-08-23 at 11 53 24 AM](https://github.com/user-attachments/assets/ebe75e0d-1904-46b0-982d-418f929d1516) **`.pointerCaptureMode`** - `Clay_PointerCaptureMode` `CLAY({ .floating = { .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_CAPTURE } })` Controls whether pointer events like hover and click should pass through to content underneath this floating element, or whether the pointer should be "captured" by this floating element. Defaults to `CLAY_POINTER_CAPTURE_MODE_CAPTURE`. **Examples** ```C // Horizontal container with three option buttons CLAY(CLAY_ID("OptionsList"), { .layout = { childGap = 16 } }) { CLAY(CLAY_IDI("Option", 1), { .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) { CLAY_TEXT(CLAY_STRING("Option 1"), CLAY_TEXT_CONFIG()); } CLAY(CLAY_IDI("Option", 2), { .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) { CLAY_TEXT(CLAY_STRING("Option 2"), CLAY_TEXT_CONFIG()); // Floating tooltip will attach above the "Option 2" container and not affect widths or positions of other elements CLAY(CLAY_ID("OptionTooltip"), { .floating = { .zIndex = 1, .attachment = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_CENTER_TOP } } }) { CLAY_TEXT(CLAY_STRING("Most popular!"), CLAY_TEXT_CONFIG()); } } CLAY(CLAY_IDI("Option", 3), { .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) { CLAY_TEXT(CLAY_STRING("Option 3"), CLAY_TEXT_CONFIG()); } } // Floating containers can also be declared elsewhere in a layout, to avoid branching or polluting other UI for (int i = 0; i < 1000; i++) { CLAY(CLAY_IDI("Option", i + 1), { }) { // ... } } // Note the use of "parentId". // Floating tooltip will attach above the "Option 2" container and not affect widths or positions of other elements CLAY(CLAY_ID("OptionTooltip"), { .floating = { .parentId = CLAY_IDI("Option", 2).id, .zIndex = 1, .attachment = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_TOP_CENTER } } }) { CLAY_TEXT(CLAY_STRING("Most popular!"), CLAY_TEXT_CONFIG()); } ``` When using `.parentId`, the floating container can be declared anywhere after `BeginLayout` and before `EndLayout`. The target element matching the `.parentId` doesn't need to exist when `Clay_FloatingElementConfig` is used. **Rendering** `Clay_FloatingElementConfig` will not generate any specific render commands. --- ### Clay_CustomElementConfig **Usage** `CLAY(CLAY_ID("Custom"), { .custom = { .customData = &something } }) {}` **Notes** **Clay_CustomElementConfig** allows the user to pass custom data to the renderer. **Struct Definition (Pseudocode)** ```C typedef struct { void * customData; } Clay_CustomElementConfig; ``` **Fields** `.customData` - `void *` `CLAY({ .custom = { .customData = &myCustomData } })` `.customData` is a generic void pointer that can be used to pass through custom data to the renderer. **Examples** ```C #include "clay.h" typedef enum { CUSTOM_ELEMENT_TYPE_MODEL, CUSTOM_ELEMENT_TYPE_VIDEO } CustomElementType; // A rough example of how you could handle laying out 3d models in your UI typedef struct { CustomElementType type; union { Model model; Video video; // ... }; } CustomElementData; Model myModel = Load3DModel(filePath); CustomElement modelElement = (CustomElement) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel } typedef struct { void* memory; uintptr_t offset; } Arena; // During init Arena frameArena = (Arena) { .memory = malloc(1024) }; // ... CLAY(0) { // Custom elements only take a single pointer, so we need to store the data somewhere CustomElementData *modelData = (CustomElementData *)(frameArena.memory + frameArena.offset); *modelData = (CustomElementData) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel }; frameArena.offset += sizeof(CustomElementData); CLAY(CLAY_ID("3DModelViewer"), { .custom = { .customData = modelData } }) {} } // Later during your rendering switch (renderCommand->commandType) { // ... case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { // Your extended struct is passed through CustomElementData *customElement = renderCommand->config.customElementConfig->customData; if (!customElement) continue; switch (customElement->type) { case CUSTOM_ELEMENT_TYPE_MODEL: { // Render your 3d model here break; } case CUSTOM_ELEMENT_TYPE_VIDEO: { // Render your video here break; } // ... } break; } } ``` **Rendering** Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand` with `commandType = CLAY_RENDER_COMMAND_TYPE_CUSTOM` will be created. ### Clay_Color ```C typedef struct { float r, g, b, a; } Clay_Color; ``` `Clay_Color` is an RGBA color struct used in Clay's declarations and rendering. By convention the channels are represented as 0-255, but this is left up to the renderer. Note: when using the debug tools, their internal colors are represented as 0-255. ### Clay_String ```C typedef struct { bool isStaticallyAllocated; int32_t length; const char *chars; } Clay_String; ``` `Clay_String` is a string container that clay uses internally to represent all strings. **Fields** **`.isStaticallyAllocated`** - `bool` Whether or not the string is statically allocated, or in other words, whether or not it lives for the entire lifetime of the program. --- **`.length`** - `int32_t` The number of characters in the string, _not including an optional null terminator._ --- **`.chars`** - `const char *` A pointer to the contents of the string. This data is not guaranteed to be null terminated, so if you are passing it to code that expects standard null terminated C strings, you will need to copy the data and append a null terminator. --- ### Clay_ElementId ```C typedef struct { uint32_t id; uint32_t offset; uint32_t baseId; Clay_String stringId; } Clay_ElementId; ``` Returned by [CLAY_ID](#clay_id) and [CLAY_IDI](#clay_idi), this struct contains a hash id, as well as the source string that was used to generate it. **Fields** **`.id`** - `uint32_t` A unique ID derived from the string passed to [CLAY_ID](#clay_id) or [CLAY_IDI](#clay_idi). --- **`.offset`** - `uint32_t` If this id was generated using [CLAY_IDI](#clay_idi), `.offset` is the value passed as the second argument. For [CLAY_ID](#clay_id), this will always be `0`. --- **`.baseId`** - `uint32_t` If this id was generated using [CLAY_IDI](#clay_idi), `.baseId` is the hash of the base string passed, **before it is additionally hashed with `.offset`**. For [CLAY_ID](#clay_id), this will always be the same as `.id`. --- **`.stringId`** - `Clay_String` Stores the original string that was passed in when [CLAY_ID](#clay_id) or [CLAY_IDI](#clay_idi) were called. --- ### Clay_RenderCommandArray ```C typedef struct { uint32_t capacity; uint32_t length; Clay_RenderCommand *internalArray; } Clay_RenderCommandArray; ``` Returned by [Clay_EndLayout](#clay_endlayout), this array contains the [Clay_RenderCommand](#clay_rendercommand)s representing the calculated layout. **Fields** **`.capacity`** - `uint32_t` Represents the total capacity of the allocated memory in `.internalArray`. --- **`.length`** - `uint32_t` Represents the total number of `Clay_RenderCommand` elements stored consecutively at the address `.internalArray`. --- **`.internalArray`** - `Clay_RenderCommand` An array of [Clay_RenderCommand](#clay_rendercommand)s representing the calculated layout. If there was at least one render command, this array will contain elements from `.internalArray[0]` to `.internalArray[.length - 1]`. --- ### Clay_RenderCommand ```C typedef struct { Clay_BoundingBox boundingBox; Clay_RenderData renderData; uintptr_t userData; uint32_t id; int16_t zIndex; Clay_RenderCommandType commandType; } Clay_RenderCommand; ``` **Fields** **`.commandType`** - `Clay_RenderCommandType` An enum indicating how this render command should be handled. Possible values include: - `CLAY_RENDER_COMMAND_TYPE_NONE` - Should be ignored by the renderer, and never emitted by clay under normal conditions. - `CLAY_RENDER_COMMAND_TYPE_RECTANGLE` - A rectangle should be drawn, configured with `.config.rectangleElementConfig` - `CLAY_RENDER_COMMAND_TYPE_BORDER` - A border should be drawn, configured with `.config.borderElementConfig` - `CLAY_RENDER_COMMAND_TYPE_TEXT` - Text should be drawn, configured with `.config.textElementConfig` - `CLAY_RENDER_COMMAND_TYPE_IMAGE` - An image should be drawn, configured with `.config.imageElementConfig` - `CLAY_RENDER_COMMAND_TYPE_SCISSOR_START` - Named after [glScissor](https://registry.khronos.org/OpenGL-Refpages/gl4/html/glScissor.xhtml), this indicates that the renderer should begin culling any subsequent pixels that are drawn outside the `.boundingBox` of this render command. - `CLAY_RENDER_COMMAND_TYPE_SCISSOR_END` - Only ever appears after a matching `CLAY_RENDER_COMMAND_TYPE_SCISSOR_START` command, and indicates that the scissor has ended. - `CLAY_RENDER_COMMAND_TYPE_CUSTOM` - A custom render command controlled by the user, configured with `.config.customElementConfig` --- **`.boundingBox`** - `Clay_BoundingBox` ```C typedef struct { float x, y, width, height; } Clay_BoundingBox; ``` A rectangle representing the bounding box of this render command, with `.x` and `.y` representing the top left corner of the element. --- **`.id`** - `uint32_t` The id that was originally used with the element macro that created this render command. See [CLAY_ID](#clay_id) for details. --- **`.zIndex`** - `int16_t` The z index of the element, based on what was passed to the root floating configuration that this element is a child of. Higher z indexes should be rendered _on top_ of lower z indexes. --- **`.renderData`** - `Clay_RenderData` ```C typedef union { Clay_RectangleRenderData rectangle; Clay_TextRenderData text; Clay_ImageRenderData image; Clay_CustomRenderData custom; Clay_BorderRenderData border; } Clay_RenderData; ``` A C union containing various structs, with the type dependent on `.commandType`. Possible values include: - `config.rectangle` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE`. - `config.text` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_TEXT`. See [Clay_Text](#clay_text) for details. - `config.image` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE`. See [Clay_Image](#clay_imageelementconfig) for details. - `config.border` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_BORDER`. See [Clay_Border](#clay_borderelementconfig) for details. - `config.custom` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM`. See [Clay_Custom](#clay_customelementconfig) for details. **Union Structs** ```C typedef struct { Clay_StringSlice stringContents; Clay_Color textColor; uint16_t fontId; uint16_t fontSize; uint16_t letterSpacing; uint16_t lineHeight; } Clay_TextRenderData; ``` ```C typedef struct { Clay_Color backgroundColor; Clay_CornerRadius cornerRadius; } Clay_RectangleRenderData; ``` ```C typedef struct { Clay_Color backgroundColor; Clay_CornerRadius cornerRadius; void* imageData; } Clay_ImageRenderData; ``` ```C typedef struct { Clay_Color backgroundColor; Clay_CornerRadius cornerRadius; void* customData; } Clay_CustomRenderData; ``` ```C typedef struct { Clay_Color color; Clay_CornerRadius cornerRadius; Clay_BorderWidth width; } Clay_BorderRenderData; ``` ```C typedef union { Clay_RectangleRenderData rectangle; Clay_TextRenderData text; Clay_ImageRenderData image; Clay_CustomRenderData custom; Clay_BorderRenderData border; } Clay_RenderData; ``` ### Clay_ScrollContainerData ```C // Data representing the current internal state of a scrolling element. typedef struct { // Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling. Clay_Vector2 *scrollPosition; // The bounding box of the scroll element. Clay_Dimensions scrollContainerDimensions; // The outer dimensions of the inner scroll container content, including the padding of the parent scroll container. Clay_Dimensions contentDimensions; // The config that was originally passed to the scroll element. Clay_ClipElementConfig config; // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. bool found; } Clay_ScrollContainerData; ``` **Fields** **`.scrollPosition`** - `Clay_Vector2 *` A pointer to the internal scroll position of this scroll container. Mutating it will result in elements inside the scroll container shifting up / down (`.y`) or left / right (`.x`). --- **`.scrollContainerDimensions`** - `Clay_Dimensions` ```C typedef struct { float width, height; } Clay_Dimensions; ``` Dimensions representing the outer width and height of the scroll container itself. --- **`.contentDimensions`** - `Clay_Dimensions` ```C typedef struct { float width, height; } Clay_Dimensions; ``` Dimensions representing the inner width and height of the content _inside_ the scroll container. Scrolling is only possible when the `contentDimensions` are larger in at least one dimension than the `scrollContainerDimensions`. --- **`.config`** - `Clay_ClipElementConfig` The [Clay_ClipElementConfig](#clay_scroll) for the matching scroll container element. --- ### Clay_ElementData ```C // Bounding box and other data for a specific UI element. typedef struct { // The rectangle that encloses this UI element, with the position relative to the root of the layout. Clay_BoundingBox boundingBox; // Indicates whether an actual Element matched the provided ID or if the default struct was returned. bool found; } Clay_ElementData; ``` **Fields** **`.boundingBox`** - `Clay_BoundingBox` ```C typedef struct { float x, y, width, height; } Clay_BoundingBox; ``` A rectangle representing the bounding box of this render command, with `.x` and `.y` representing the top left corner of the element. --- **`.found`** - `bool` A boolean representing whether or not the ID passed to [Clay_GetElementData](#clay_getelementdata) matched a valid element or not. In the case that `.found` is `false`, `.boundingBox` will be the default value (zeroed). --- ### Clay_PointerData ```C typedef struct { Clay_Vector2 position; Clay_PointerDataInteractionState state; } Clay_PointerData; ``` **Fields** **`.position`** - `Clay_Vector2` A Vector2 containing the current x,y coordinates of the mouse pointer, which were originally passed into [Clay_SetPointerState()](#clay_setpointerstate). --- **`.state`** - `Clay_PointerDataInteractionState` ```C typedef enum { CLAY_POINTER_DATA_PRESSED_THIS_FRAME, CLAY_POINTER_DATA_PRESSED, CLAY_POINTER_DATA_RELEASED_THIS_FRAME, CLAY_POINTER_DATA_RELEASED, } Clay_PointerDataInteractionState; ``` An enum value representing the current "state" of the pointer interaction. As an example, consider the case where a user is on a desktop computer, moves the mouse pointer over a button, clicks and holds the left mouse button for a short time, then releases it: - While the mouse pointer is over ("hovering") the button, but no mouse button has been pressed: `CLAY_POINTER_DATA_RELEASED` - First frame that the user presses the left mouse button: `CLAY_POINTER_DATA_PRESSED_THIS_FRAME` - All subsequent frames where the user is still holding the left mouse button: `CLAY_POINTER_DATA_PRESSED` - The single frame where the left mouse button goes from pressed -> released: `CLAY_POINTER_DATA_RELEASED_THIS_FRAME` - All subsequent frames while the mouse pointer is still over the button: `CLAY_POINTER_DATA_RELEASED` --- ### Clay_ErrorHandler ```C typedef struct { void (*errorHandlerFunction)(Clay_ErrorData errorText); uintptr_t userData; } Clay_ErrorHandler; ``` **Fields** **`.errorHandlerFunction`** - `void (Clay_ErrorData errorText) {}` A function pointer to an error handler function, which takes `Clay_ErrorData` as an argument. This function will be called whenever Clay encounters an internal error. --- **`.userData`** - `uintptr_t` A generic pointer to extra userdata that is transparently passed through from `Clay_Initialize` to Clay's error handler callback. Defaults to NULL. --- ### Clay_ErrorData ```C typedef struct { Clay_ErrorType errorType; Clay_String errorText; uintptr_t userData; } Clay_ErrorData; ``` **Fields** **`.errorType`** - `Clay_ErrorType` ```C typedef enum { CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, CLAY_ERROR_TYPE_DUPLICATE_ID, CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, CLAY_ERROR_TYPE_INTERNAL_ERROR, } Clay_ErrorType; ``` An enum representing the type of error Clay encountered. It's up to the user to handle on a case by case basis, but as some general guidance: - `CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED` - The user is attempting to use `CLAY_TEXT` and either forgot to call [Clay_SetMeasureTextFunction](#clay_setmeasuretextfunction) or accidentally passed a null function pointer. - `CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED` - Clay was initialized with an Arena that was too small for the configured [Clay_SetMaxElementCount](#clay_setmaxelementcount). Try using [Clay_MinMemorySize()](#clay_minmemorysize) to get the exact number of bytes required by the current configuration. - `CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED` - The declared UI hierarchy has too many elements for the configured max element count. Use [Clay_SetMaxElementCount](#clay_setmaxelementcount) to increase the max, then call [Clay_MinMemorySize()](#clay_minmemorysize) again and reinitialize clay's memory with the required size. - `CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED` - The declared UI hierarchy has too much text for the configured text measure cache size. Use [Clay_SetMaxMeasureTextCacheWordCount](#clay_setmeasuretextcachesize) to increase the max, then call [Clay_MinMemorySize()](#clay_minmemorysize) again and reinitialize clay's memory with the required size. - `CLAY_ERROR_TYPE_DUPLICATE_ID` - Two elements in Clays UI Hierarchy have been declared with exactly the same ID. Set a breakpoint in your error handler function for a stack trace back to exactly where this occured. - `CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND` - A `CLAY_FLOATING` element was declared with the `.parentId` property, but no element with that ID was found. Set a breakpoint in your error handler function for a stack trace back to exactly where this occured. - `CLAY_ERROR_TYPE_INTERNAL_ERROR` - Clay has encountered an internal logic or memory error. Please report this as a bug with a stack trace to help us fix these! --- **`.errorText`** - `Clay_String` A [Clay_String](#clay_string) that provides a human readable description of the error. May change in future and should not be relied on to detect error types. --- **`.userData`** - `uintptr_t` A generic pointer to extra userdata that is transparently passed through from `Clay_Initialize` to Clay's error handler callback. Defaults to NULL. --- ================================================ FILE: bindings/cpp/README.md ================================================ https://github.com/TimothyHoytBSME/ClayMan ================================================ FILE: bindings/csharp/README ================================================ https://github.com/Orcolom/clay-cs ================================================ FILE: bindings/rust/README ================================================ https://github.com/clay-ui-rs/clay https://crates.io/crates/clay-layout ================================================ FILE: bindings/zig/README ================================================ https://codeberg.org/Zettexe/clay-zig https://github.com/johan0A/clay-zig-bindings ================================================ FILE: clay.h ================================================ // VERSION: 0.14 /* NOTE: In order to use this library you must define the following macro in exactly one file, _before_ including clay.h: #define CLAY_IMPLEMENTATION #include "clay.h" See the examples folder for details. */ #include #include #include // SIMD includes on supported platforms #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) #include #elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) #include #endif // ----------------------------------------- // HEADER DECLARATIONS --------------------- // ----------------------------------------- #ifndef CLAY_HEADER #define CLAY_HEADER #if !( \ (defined(__cplusplus) && __cplusplus >= 202002L) || \ (defined(__STDC__) && __STDC__ == 1 && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ defined(_MSC_VER) || \ defined(__OBJC__) \ ) #error "Clay requires C99, C++20, or MSVC" #endif #ifdef CLAY_WASM #define CLAY_WASM_EXPORT(name) __attribute__((export_name(name))) #else #define CLAY_WASM_EXPORT(null) #endif #ifdef CLAY_DLL #define CLAY_DLL_EXPORT __declspec(dllexport) __stdcall #else #define CLAY_DLL_EXPORT #endif // Public Macro API ------------------------ #define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y)) #define CLAY__MIN(x, y) (((x) < (y)) ? (x) : (y)) #define CLAY_TEXT_CONFIG(...) Clay__StoreTextElementConfig(CLAY__CONFIG_WRAPPER(Clay_TextElementConfig, __VA_ARGS__)) #define CLAY_BORDER_OUTSIDE(widthValue) {widthValue, widthValue, widthValue, widthValue, 0} #define CLAY_BORDER_ALL(widthValue) {widthValue, widthValue, widthValue, widthValue, widthValue} #define CLAY_CORNER_RADIUS(radius) (CLAY__INIT(Clay_CornerRadius) { radius, radius, radius, radius }) #define CLAY_PADDING_ALL(padding) CLAY__CONFIG_WRAPPER(Clay_Padding, { padding, padding, padding, padding }) #define CLAY_SIZING_FIT(...) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { __VA_ARGS__ } }, .type = CLAY__SIZING_TYPE_FIT }) #define CLAY_SIZING_GROW(...) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { __VA_ARGS__ } }, .type = CLAY__SIZING_TYPE_GROW }) #define CLAY_SIZING_FIXED(fixedSize) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { fixedSize, fixedSize } }, .type = CLAY__SIZING_TYPE_FIXED }) #define CLAY_SIZING_PERCENT(percentOfParent) (CLAY__INIT(Clay_SizingAxis) { .size = { .percent = (percentOfParent) }, .type = CLAY__SIZING_TYPE_PERCENT }) // Note: If a compile error led you here, you might be trying to use CLAY_ID with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID instead. #define CLAY_ID(label) CLAY_SID(CLAY_STRING(label)) #define CLAY_SID(label) Clay__HashString(label, 0) // Note: If a compile error led you here, you might be trying to use CLAY_IDI with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI instead. #define CLAY_IDI(label, index) CLAY_SIDI(CLAY_STRING(label), index) #define CLAY_SIDI(label, index) Clay__HashStringWithOffset(label, index, 0) // Note: If a compile error led you here, you might be trying to use CLAY_ID_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID_LOCAL instead. #define CLAY_ID_LOCAL(label) CLAY_SID_LOCAL(CLAY_STRING(label)) #define CLAY_SID_LOCAL(label) Clay__HashString(label, Clay__GetParentElementId()) // Note: If a compile error led you here, you might be trying to use CLAY_IDI_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI_LOCAL instead. #define CLAY_IDI_LOCAL(label, index) CLAY_SIDI_LOCAL(CLAY_STRING(label), index) #define CLAY_SIDI_LOCAL(label, index) Clay__HashStringWithOffset(label, index, Clay__GetParentElementId()) #define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0])) #define CLAY__ENSURE_STRING_LITERAL(x) ("" x "") // Note: If an error led you here, it's because CLAY_STRING can only be used with string literals, i.e. CLAY_STRING("SomeString") and not CLAY_STRING(yourString) #define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }) #define CLAY_STRING_CONST(string) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) } static uint8_t CLAY__ELEMENT_DEFINITION_LATCH; // GCC marks the above CLAY__ELEMENT_DEFINITION_LATCH as an unused variable for files that include clay.h but don't declare any layout // This is to suppress that warning static inline void Clay__SuppressUnusedLatchDefinitionVariableWarning(void) { (void) CLAY__ELEMENT_DEFINITION_LATCH; } // Publicly visible layout element macros ----------------------------------------------------- /* This macro looks scary on the surface, but is actually quite simple. It turns a macro call like this: CLAY({ .id = CLAY_ID("Container"), .backgroundColor = { 255, 200, 200, 255 } }) { ...children declared here } Into calls like this: Clay__OpenElement(); Clay__ConfigureOpenElement((Clay_ElementDeclaration) { .id = CLAY_ID("Container"), .backgroundColor = { 255, 200, 200, 255 } }); ...children declared here Clay__CloseElement(); The for loop will only ever run a single iteration, putting Clay__CloseElement() in the increment of the loop means that it will run after the body - where the children are declared. It just exists to make sure you don't forget to call Clay_CloseElement(). */ #define CLAY_AUTO_ID(...) \ for ( \ CLAY__ELEMENT_DEFINITION_LATCH = (Clay__OpenElement(), Clay__ConfigureOpenElement(CLAY__CONFIG_WRAPPER(Clay_ElementDeclaration, __VA_ARGS__)), 0); \ CLAY__ELEMENT_DEFINITION_LATCH < 1; \ CLAY__ELEMENT_DEFINITION_LATCH=1, Clay__CloseElement() \ ) #define CLAY(id, ...) \ for ( \ CLAY__ELEMENT_DEFINITION_LATCH = (Clay__OpenElementWithId(id), Clay__ConfigureOpenElement(CLAY__CONFIG_WRAPPER(Clay_ElementDeclaration, __VA_ARGS__)), 0); \ CLAY__ELEMENT_DEFINITION_LATCH < 1; \ CLAY__ELEMENT_DEFINITION_LATCH=1, Clay__CloseElement() \ ) // These macros exist to allow the CLAY() macro to be called both with an inline struct definition, such as // CLAY({ .id = something... }); // As well as by passing a predefined declaration struct // Clay_ElementDeclaration declarationStruct = ... // CLAY(declarationStruct); #define CLAY__WRAPPER_TYPE(type) Clay__##type##Wrapper #define CLAY__WRAPPER_STRUCT(type) typedef struct { type wrapped; } CLAY__WRAPPER_TYPE(type) #define CLAY__CONFIG_WRAPPER(type, ...) (CLAY__INIT(CLAY__WRAPPER_TYPE(type)) { __VA_ARGS__ }).wrapped #define CLAY_TEXT(text, textConfig) Clay__OpenTextElement(text, textConfig) #ifdef __cplusplus #define CLAY__INIT(type) type #define CLAY_PACKED_ENUM enum : uint8_t #define CLAY__DEFAULT_STRUCT {} #else #define CLAY__INIT(type) (type) #if defined(_MSC_VER) && !defined(__clang__) #define CLAY_PACKED_ENUM __pragma(pack(push, 1)) enum __pragma(pack(pop)) #else #define CLAY_PACKED_ENUM enum __attribute__((__packed__)) #endif #if __STDC_VERSION__ >= 202311L #define CLAY__DEFAULT_STRUCT {} #else #define CLAY__DEFAULT_STRUCT {0} #endif #endif // __cplusplus #ifdef __cplusplus extern "C" { #endif // Utility Structs ------------------------- // Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string, // but it is also used to represent slices. typedef struct Clay_String { // Set this boolean to true if the char* data underlying this string will live for the entire lifetime of the program. // This will automatically be set for strings created with CLAY_STRING, as the macro requires a string literal. bool isStaticallyAllocated; int32_t length; // The underlying character memory. Note: this will not be copied and will not extend the lifetime of the underlying memory. const char *chars; } Clay_String; // Clay_StringSlice is used to represent non owning string slices, and includes // a baseChars field which points to the string this slice is derived from. typedef struct Clay_StringSlice { int32_t length; const char *chars; const char *baseChars; // The source string / char* that this slice was derived from } Clay_StringSlice; typedef struct Clay_Context Clay_Context; // Clay_Arena is a memory arena structure that is used by clay to manage its internal allocations. // Rather than creating it by hand, it's easier to use Clay_CreateArenaWithCapacityAndMemory() typedef struct Clay_Arena { uintptr_t nextAllocation; size_t capacity; char *memory; } Clay_Arena; typedef struct Clay_Dimensions { float width, height; } Clay_Dimensions; typedef struct Clay_Vector2 { float x, y; } Clay_Vector2; // Internally clay conventionally represents colors as 0-255, but interpretation is up to the renderer. typedef struct Clay_Color { float r, g, b, a; } Clay_Color; typedef struct Clay_BoundingBox { float x, y, width, height; } Clay_BoundingBox; // Primarily created via the CLAY_ID(), CLAY_IDI(), CLAY_ID_LOCAL() and CLAY_IDI_LOCAL() macros. // Represents a hashed string ID used for identifying and finding specific clay UI elements, required // by functions such as Clay_PointerOver() and Clay_GetElementData(). typedef struct Clay_ElementId { uint32_t id; // The resulting hash generated from the other fields. uint32_t offset; // A numerical offset applied after computing the hash from stringId. uint32_t baseId; // A base hash value to start from, for example the parent element ID is used when calculating CLAY_ID_LOCAL(). Clay_String stringId; // The string id to hash. } Clay_ElementId; // A sized array of Clay_ElementId. typedef struct { int32_t capacity; int32_t length; Clay_ElementId *internalArray; } Clay_ElementIdArray; // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. typedef struct Clay_CornerRadius { float topLeft; float topRight; float bottomLeft; float bottomRight; } Clay_CornerRadius; // Element Configs --------------------------- // Controls the direction in which child elements will be automatically laid out. typedef CLAY_PACKED_ENUM { // (Default) Lays out child elements from left to right with increasing x. CLAY_LEFT_TO_RIGHT, // Lays out child elements from top to bottom with increasing y. CLAY_TOP_TO_BOTTOM, } Clay_LayoutDirection; // Controls the alignment along the x axis (horizontal) of child elements. typedef CLAY_PACKED_ENUM { // (Default) Aligns child elements to the left hand side of this element, offset by padding.width.left CLAY_ALIGN_X_LEFT, // Aligns child elements to the right hand side of this element, offset by padding.width.right CLAY_ALIGN_X_RIGHT, // Aligns child elements horizontally to the center of this element CLAY_ALIGN_X_CENTER, } Clay_LayoutAlignmentX; // Controls the alignment along the y axis (vertical) of child elements. typedef CLAY_PACKED_ENUM { // (Default) Aligns child elements to the top of this element, offset by padding.width.top CLAY_ALIGN_Y_TOP, // Aligns child elements to the bottom of this element, offset by padding.width.bottom CLAY_ALIGN_Y_BOTTOM, // Aligns child elements vertically to the center of this element CLAY_ALIGN_Y_CENTER, } Clay_LayoutAlignmentY; // Controls how the element takes up space inside its parent container. typedef CLAY_PACKED_ENUM { // (default) Wraps tightly to the size of the element's contents. CLAY__SIZING_TYPE_FIT, // Expands along this axis to fill available space in the parent element, sharing it with other GROW elements. CLAY__SIZING_TYPE_GROW, // Expects 0-1 range. Clamps the axis size to a percent of the parent container's axis size minus padding and child gaps. CLAY__SIZING_TYPE_PERCENT, // Clamps the axis size to an exact size in pixels. CLAY__SIZING_TYPE_FIXED, } Clay__SizingType; // Controls how child elements are aligned on each axis. typedef struct Clay_ChildAlignment { Clay_LayoutAlignmentX x; // Controls alignment of children along the x axis. Clay_LayoutAlignmentY y; // Controls alignment of children along the y axis. } Clay_ChildAlignment; // Controls the minimum and maximum size in pixels that this element is allowed to grow or shrink to, // overriding sizing types such as FIT or GROW. typedef struct Clay_SizingMinMax { float min; // The smallest final size of the element on this axis will be this value in pixels. float max; // The largest final size of the element on this axis will be this value in pixels. } Clay_SizingMinMax; // Controls the sizing of this element along one axis inside its parent container. typedef struct Clay_SizingAxis { union { Clay_SizingMinMax minMax; // Controls the minimum and maximum size in pixels that this element is allowed to grow or shrink to, overriding sizing types such as FIT or GROW. float percent; // Expects 0-1 range. Clamps the axis size to a percent of the parent container's axis size minus padding and child gaps. } size; Clay__SizingType type; // Controls how the element takes up space inside its parent container. } Clay_SizingAxis; // Controls the sizing of this element along one axis inside its parent container. typedef struct Clay_Sizing { Clay_SizingAxis width; // Controls the width sizing of the element, along the x axis. Clay_SizingAxis height; // Controls the height sizing of the element, along the y axis. } Clay_Sizing; // Controls "padding" in pixels, which is a gap between the bounding box of this element and where its children // will be placed. typedef struct Clay_Padding { uint16_t left; uint16_t right; uint16_t top; uint16_t bottom; } Clay_Padding; CLAY__WRAPPER_STRUCT(Clay_Padding); // Controls various settings that affect the size and position of an element, as well as the sizes and positions // of any child elements. typedef struct Clay_LayoutConfig { Clay_Sizing sizing; // Controls the sizing of this element inside it's parent container, including FIT, GROW, PERCENT and FIXED sizing. Clay_Padding padding; // Controls "padding" in pixels, which is a gap between the bounding box of this element and where its children will be placed. uint16_t childGap; // Controls the gap in pixels between child elements along the layout axis (horizontal gap for LEFT_TO_RIGHT, vertical gap for TOP_TO_BOTTOM). Clay_ChildAlignment childAlignment; // Controls how child elements are aligned on each axis. Clay_LayoutDirection layoutDirection; // Controls the direction in which child elements will be automatically laid out. } Clay_LayoutConfig; CLAY__WRAPPER_STRUCT(Clay_LayoutConfig); extern Clay_LayoutConfig CLAY_LAYOUT_DEFAULT; // Controls how text "wraps", that is how it is broken into multiple lines when there is insufficient horizontal space. typedef CLAY_PACKED_ENUM { // (default) breaks on whitespace characters. CLAY_TEXT_WRAP_WORDS, // Don't break on space characters, only on newlines. CLAY_TEXT_WRAP_NEWLINES, // Disable text wrapping entirely. CLAY_TEXT_WRAP_NONE, } Clay_TextElementConfigWrapMode; // Controls how wrapped lines of text are horizontally aligned within the outer text bounding box. typedef CLAY_PACKED_ENUM { // (default) Horizontally aligns wrapped lines of text to the left hand side of their bounding box. CLAY_TEXT_ALIGN_LEFT, // Horizontally aligns wrapped lines of text to the center of their bounding box. CLAY_TEXT_ALIGN_CENTER, // Horizontally aligns wrapped lines of text to the right hand side of their bounding box. CLAY_TEXT_ALIGN_RIGHT, } Clay_TextAlignment; // Controls various functionality related to text elements. typedef struct Clay_TextElementConfig { // A pointer that will be transparently passed through to the resulting render command. void *userData; // The RGBA color of the font to render, conventionally specified as 0-255. Clay_Color textColor; // An integer transparently passed to Clay_MeasureText to identify the font to use. // The debug view will pass fontId = 0 for its internal text. uint16_t fontId; // Controls the size of the font. Handled by the function provided to Clay_MeasureText. uint16_t fontSize; // Controls extra horizontal spacing between characters. Handled by the function provided to Clay_MeasureText. uint16_t letterSpacing; // Controls additional vertical space between wrapped lines of text. uint16_t lineHeight; // Controls how text "wraps", that is how it is broken into multiple lines when there is insufficient horizontal space. // CLAY_TEXT_WRAP_WORDS (default) breaks on whitespace characters. // CLAY_TEXT_WRAP_NEWLINES doesn't break on space characters, only on newlines. // CLAY_TEXT_WRAP_NONE disables wrapping entirely. Clay_TextElementConfigWrapMode wrapMode; // Controls how wrapped lines of text are horizontally aligned within the outer text bounding box. // CLAY_TEXT_ALIGN_LEFT (default) - Horizontally aligns wrapped lines of text to the left hand side of their bounding box. // CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box. // CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box. Clay_TextAlignment textAlignment; } Clay_TextElementConfig; CLAY__WRAPPER_STRUCT(Clay_TextElementConfig); // Aspect Ratio -------------------------------- // Controls various settings related to aspect ratio scaling element. typedef struct Clay_AspectRatioElementConfig { float aspectRatio; // A float representing the target "Aspect ratio" for an element, which is its final width divided by its final height. } Clay_AspectRatioElementConfig; CLAY__WRAPPER_STRUCT(Clay_AspectRatioElementConfig); // Image -------------------------------- // Controls various settings related to image elements. typedef struct Clay_ImageElementConfig { void* imageData; // A transparent pointer used to pass image data through to the renderer. } Clay_ImageElementConfig; CLAY__WRAPPER_STRUCT(Clay_ImageElementConfig); // Floating ----------------------------- // Controls where a floating element is offset relative to its parent element. // Note: see https://github.com/user-attachments/assets/b8c6dfaa-c1b1-41a4-be55-013473e4a6ce for a visual explanation. typedef CLAY_PACKED_ENUM { CLAY_ATTACH_POINT_LEFT_TOP, CLAY_ATTACH_POINT_LEFT_CENTER, CLAY_ATTACH_POINT_LEFT_BOTTOM, CLAY_ATTACH_POINT_CENTER_TOP, CLAY_ATTACH_POINT_CENTER_CENTER, CLAY_ATTACH_POINT_CENTER_BOTTOM, CLAY_ATTACH_POINT_RIGHT_TOP, CLAY_ATTACH_POINT_RIGHT_CENTER, CLAY_ATTACH_POINT_RIGHT_BOTTOM, } Clay_FloatingAttachPointType; // Controls where a floating element is offset relative to its parent element. typedef struct Clay_FloatingAttachPoints { Clay_FloatingAttachPointType element; // Controls the origin point on a floating element that attaches to its parent. Clay_FloatingAttachPointType parent; // Controls the origin point on the parent element that the floating element attaches to. } Clay_FloatingAttachPoints; // Controls how mouse pointer events like hover and click are captured or passed through to elements underneath a floating element. typedef CLAY_PACKED_ENUM { // (default) "Capture" the pointer event and don't allow events like hover and click to pass through to elements underneath. CLAY_POINTER_CAPTURE_MODE_CAPTURE, // CLAY_POINTER_CAPTURE_MODE_PARENT, TODO pass pointer through to attached parent // Transparently pass through pointer events like hover and click to elements underneath the floating element. CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, } Clay_PointerCaptureMode; // Controls which element a floating element is "attached" to (i.e. relative offset from). typedef CLAY_PACKED_ENUM { // (default) Disables floating for this element. CLAY_ATTACH_TO_NONE, // Attaches this floating element to its parent, positioned based on the .attachPoints and .offset fields. CLAY_ATTACH_TO_PARENT, // Attaches this floating element to an element with a specific ID, specified with the .parentId field. positioned based on the .attachPoints and .offset fields. CLAY_ATTACH_TO_ELEMENT_WITH_ID, // Attaches this floating element to the root of the layout, which combined with the .offset field provides functionality similar to "absolute positioning". CLAY_ATTACH_TO_ROOT, } Clay_FloatingAttachToElement; // Controls whether or not a floating element is clipped to the same clipping rectangle as the element it's attached to. typedef CLAY_PACKED_ENUM { // (default) - The floating element does not inherit clipping. CLAY_CLIP_TO_NONE, // The floating element is clipped to the same clipping rectangle as the element it's attached to. CLAY_CLIP_TO_ATTACHED_PARENT } Clay_FloatingClipToElement; // Controls various settings related to "floating" elements, which are elements that "float" above other elements, potentially overlapping their boundaries, // and not affecting the layout of sibling or parent elements. typedef struct Clay_FloatingElementConfig { // Offsets this floating element by the provided x,y coordinates from its attachPoints. Clay_Vector2 offset; // Expands the boundaries of the outer floating element without affecting its children. Clay_Dimensions expand; // When used in conjunction with .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, attaches this floating element to the element in the hierarchy with the provided ID. // Hint: attach the ID to the other element with .id = CLAY_ID("yourId"), and specify the id the same way, with .parentId = CLAY_ID("yourId").id uint32_t parentId; // Controls the z index of this floating element and all its children. Floating elements are sorted in ascending z order before output. // zIndex is also passed to the renderer for all elements contained within this floating element. int16_t zIndex; // Controls how mouse pointer events like hover and click are captured or passed through to elements underneath / behind a floating element. // Enum is of the form CLAY_ATTACH_POINT_foo_bar. See Clay_FloatingAttachPoints for more details. // Note: see for a visual explanation. Clay_FloatingAttachPoints attachPoints; // Controls how mouse pointer events like hover and click are captured or passed through to elements underneath a floating element. // CLAY_POINTER_CAPTURE_MODE_CAPTURE (default) - "Capture" the pointer event and don't allow events like hover and click to pass through to elements underneath. // CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH - Transparently pass through pointer events like hover and click to elements underneath the floating element. Clay_PointerCaptureMode pointerCaptureMode; // Controls which element a floating element is "attached" to (i.e. relative offset from). // CLAY_ATTACH_TO_NONE (default) - Disables floating for this element. // CLAY_ATTACH_TO_PARENT - Attaches this floating element to its parent, positioned based on the .attachPoints and .offset fields. // CLAY_ATTACH_TO_ELEMENT_WITH_ID - Attaches this floating element to an element with a specific ID, specified with the .parentId field. positioned based on the .attachPoints and .offset fields. // CLAY_ATTACH_TO_ROOT - Attaches this floating element to the root of the layout, which combined with the .offset field provides functionality similar to "absolute positioning". Clay_FloatingAttachToElement attachTo; // Controls whether or not a floating element is clipped to the same clipping rectangle as the element it's attached to. // CLAY_CLIP_TO_NONE (default) - The floating element does not inherit clipping. // CLAY_CLIP_TO_ATTACHED_PARENT - The floating element is clipped to the same clipping rectangle as the element it's attached to. Clay_FloatingClipToElement clipTo; } Clay_FloatingElementConfig; CLAY__WRAPPER_STRUCT(Clay_FloatingElementConfig); // Custom ----------------------------- // Controls various settings related to custom elements. typedef struct Clay_CustomElementConfig { // A transparent pointer through which you can pass custom data to the renderer. // Generates CUSTOM render commands. void* customData; } Clay_CustomElementConfig; CLAY__WRAPPER_STRUCT(Clay_CustomElementConfig); // Scroll ----------------------------- // Controls the axis on which an element switches to "scrolling", which clips the contents and allows scrolling in that direction. typedef struct Clay_ClipElementConfig { bool horizontal; // Clip overflowing elements on the X axis. bool vertical; // Clip overflowing elements on the Y axis. Clay_Vector2 childOffset; // Offsets the x,y positions of all child elements. Used primarily for scrolling containers. } Clay_ClipElementConfig; CLAY__WRAPPER_STRUCT(Clay_ClipElementConfig); // Border ----------------------------- // Controls the widths of individual element borders. typedef struct Clay_BorderWidth { uint16_t left; uint16_t right; uint16_t top; uint16_t bottom; // Creates borders between each child element, depending on the .layoutDirection. // e.g. for LEFT_TO_RIGHT, borders will be vertical lines, and for TOP_TO_BOTTOM borders will be horizontal lines. // .betweenChildren borders will result in individual RECTANGLE render commands being generated. uint16_t betweenChildren; } Clay_BorderWidth; // Controls settings related to element borders. typedef struct Clay_BorderElementConfig { Clay_Color color; // Controls the color of all borders with width > 0. Conventionally represented as 0-255, but interpretation is up to the renderer. Clay_BorderWidth width; // Controls the widths of individual borders. At least one of these should be > 0 for a BORDER render command to be generated. } Clay_BorderElementConfig; CLAY__WRAPPER_STRUCT(Clay_BorderElementConfig); // Render Command Data ----------------------------- // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_TEXT typedef struct Clay_TextRenderData { // A string slice containing the text to be rendered. // Note: this is not guaranteed to be null terminated. Clay_StringSlice stringContents; // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. Clay_Color textColor; // An integer representing the font to use to render this text, transparently passed through from the text declaration. uint16_t fontId; uint16_t fontSize; // Specifies the extra whitespace gap in pixels between each character. uint16_t letterSpacing; // The height of the bounding box for this line of text. uint16_t lineHeight; } Clay_TextRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE typedef struct Clay_RectangleRenderData { // The solid background color to fill this rectangle with. Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. Clay_Color backgroundColor; // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. Clay_CornerRadius cornerRadius; } Clay_RectangleRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE typedef struct Clay_ImageRenderData { // The tint color for this image. Note that the default value is 0,0,0,0 and should likely be interpreted // as "untinted". // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. Clay_Color backgroundColor; // Controls the "radius", or corner rounding of this image. // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. Clay_CornerRadius cornerRadius; // A pointer transparently passed through from the original element definition, typically used to represent image data. void* imageData; } Clay_ImageRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM typedef struct Clay_CustomRenderData { // Passed through from .backgroundColor in the original element declaration. // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. Clay_Color backgroundColor; // Controls the "radius", or corner rounding of this custom element. // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. Clay_CornerRadius cornerRadius; // A pointer transparently passed through from the original element definition. void* customData; } Clay_CustomRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START || commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_END typedef struct Clay_ScrollRenderData { bool horizontal; bool vertical; } Clay_ClipRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_BORDER typedef struct Clay_BorderRenderData { // Controls a shared color for all this element's borders. // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. Clay_Color color; // Specifies the "radius", or corner rounding of this border element. // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. Clay_CornerRadius cornerRadius; // Controls individual border side widths. Clay_BorderWidth width; } Clay_BorderRenderData; // A struct union containing data specific to this command's .commandType typedef union Clay_RenderData { // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE Clay_RectangleRenderData rectangle; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_TEXT Clay_TextRenderData text; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE Clay_ImageRenderData image; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM Clay_CustomRenderData custom; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_BORDER Clay_BorderRenderData border; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START|END Clay_ClipRenderData clip; } Clay_RenderData; // Miscellaneous Structs & Enums --------------------------------- // Data representing the current internal state of a scrolling element. typedef struct Clay_ScrollContainerData { // Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling. Clay_Vector2 *scrollPosition; // The bounding box of the scroll element. Clay_Dimensions scrollContainerDimensions; // The outer dimensions of the inner scroll container content, including the padding of the parent scroll container. Clay_Dimensions contentDimensions; // The config that was originally passed to the clip element. Clay_ClipElementConfig config; // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. bool found; } Clay_ScrollContainerData; // Bounding box and other data for a specific UI element. typedef struct Clay_ElementData { // The rectangle that encloses this UI element, with the position relative to the root of the layout. Clay_BoundingBox boundingBox; // Indicates whether an actual Element matched the provided ID or if the default struct was returned. bool found; } Clay_ElementData; // Used by renderers to determine specific handling for each render command. typedef CLAY_PACKED_ENUM { // This command type should be skipped. CLAY_RENDER_COMMAND_TYPE_NONE, // The renderer should draw a solid color rectangle. CLAY_RENDER_COMMAND_TYPE_RECTANGLE, // The renderer should draw a colored border inset into the bounding box. CLAY_RENDER_COMMAND_TYPE_BORDER, // The renderer should draw text. CLAY_RENDER_COMMAND_TYPE_TEXT, // The renderer should draw an image. CLAY_RENDER_COMMAND_TYPE_IMAGE, // The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox. CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, // The renderer should finish any previously active clipping, and begin rendering elements in full again. CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, // The renderer should provide a custom implementation for handling this render command based on its .customData CLAY_RENDER_COMMAND_TYPE_CUSTOM, } Clay_RenderCommandType; typedef struct Clay_RenderCommand { // A rectangular box that fully encloses this UI element, with the position relative to the root of the layout. Clay_BoundingBox boundingBox; // A struct union containing data specific to this command's commandType. Clay_RenderData renderData; // A pointer transparently passed through from the original element declaration. void *userData; // The id of this element, transparently passed through from the original element declaration. uint32_t id; // The z order required for drawing this command correctly. // Note: the render command array is already sorted in ascending order, and will produce correct results if drawn in naive order. // This field is intended for use in batching renderers for improved performance. int16_t zIndex; // Specifies how to handle rendering of this command. // CLAY_RENDER_COMMAND_TYPE_RECTANGLE - The renderer should draw a solid color rectangle. // CLAY_RENDER_COMMAND_TYPE_BORDER - The renderer should draw a colored border inset into the bounding box. // CLAY_RENDER_COMMAND_TYPE_TEXT - The renderer should draw text. // CLAY_RENDER_COMMAND_TYPE_IMAGE - The renderer should draw an image. // CLAY_RENDER_COMMAND_TYPE_SCISSOR_START - The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox. // CLAY_RENDER_COMMAND_TYPE_SCISSOR_END - The renderer should finish any previously active clipping, and begin rendering elements in full again. // CLAY_RENDER_COMMAND_TYPE_CUSTOM - The renderer should provide a custom implementation for handling this render command based on its .customData Clay_RenderCommandType commandType; } Clay_RenderCommand; // A sized array of render commands. typedef struct Clay_RenderCommandArray { // The underlying max capacity of the array, not necessarily all initialized. int32_t capacity; // The number of initialized elements in this array. Used for loops and iteration. int32_t length; // A pointer to the first element in the internal array. Clay_RenderCommand* internalArray; } Clay_RenderCommandArray; // Represents the current state of interaction with clay this frame. typedef CLAY_PACKED_ENUM { // A left mouse click, or touch occurred this frame. CLAY_POINTER_DATA_PRESSED_THIS_FRAME, // The left mouse button click or touch happened at some point in the past, and is still currently held down this frame. CLAY_POINTER_DATA_PRESSED, // The left mouse button click or touch was released this frame. CLAY_POINTER_DATA_RELEASED_THIS_FRAME, // The left mouse button click or touch is not currently down / was released at some point in the past. CLAY_POINTER_DATA_RELEASED, } Clay_PointerDataInteractionState; // Information on the current state of pointer interactions this frame. typedef struct Clay_PointerData { // The position of the mouse / touch / pointer relative to the root of the layout. Clay_Vector2 position; // Represents the current state of interaction with clay this frame. // CLAY_POINTER_DATA_PRESSED_THIS_FRAME - A left mouse click, or touch occurred this frame. // CLAY_POINTER_DATA_PRESSED - The left mouse button click or touch happened at some point in the past, and is still currently held down this frame. // CLAY_POINTER_DATA_RELEASED_THIS_FRAME - The left mouse button click or touch was released this frame. // CLAY_POINTER_DATA_RELEASED - The left mouse button click or touch is not currently down / was released at some point in the past. Clay_PointerDataInteractionState state; } Clay_PointerData; typedef struct Clay_ElementDeclaration { // Controls various settings that affect the size and position of an element, as well as the sizes and positions of any child elements. Clay_LayoutConfig layout; // Controls the background color of the resulting element. // By convention specified as 0-255, but interpretation is up to the renderer. // If no other config is specified, .backgroundColor will generate a RECTANGLE render command, otherwise it will be passed as a property to IMAGE or CUSTOM render commands. Clay_Color backgroundColor; // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. Clay_CornerRadius cornerRadius; // Controls settings related to aspect ratio scaling. Clay_AspectRatioElementConfig aspectRatio; // Controls settings related to image elements. Clay_ImageElementConfig image; // Controls whether and how an element "floats", which means it layers over the top of other elements in z order, and doesn't affect the position and size of siblings or parent elements. // Note: in order to activate floating, .floating.attachTo must be set to something other than the default value. Clay_FloatingElementConfig floating; // Used to create CUSTOM render commands, usually to render element types not supported by Clay. Clay_CustomElementConfig custom; // Controls whether an element should clip its contents, as well as providing child x,y offset configuration for scrolling. Clay_ClipElementConfig clip; // Controls settings related to element borders, and will generate BORDER render commands. Clay_BorderElementConfig border; // A pointer that will be transparently passed through to resulting render commands. void *userData; } Clay_ElementDeclaration; CLAY__WRAPPER_STRUCT(Clay_ElementDeclaration); // Represents the type of error clay encountered while computing layout. typedef CLAY_PACKED_ENUM { // A text measurement function wasn't provided using Clay_SetMeasureTextFunction(), or the provided function was null. CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, // Clay attempted to allocate its internal data structures but ran out of space. // The arena passed to Clay_Initialize was created with a capacity smaller than that required by Clay_MinMemorySize(). CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, // Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxElementCount(). CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, // Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxMeasureTextCacheWordCount(). CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, // Two elements were declared with exactly the same ID within one layout. CLAY_ERROR_TYPE_DUPLICATE_ID, // A floating element was declared using CLAY_ATTACH_TO_ELEMENT_ID and either an invalid .parentId was provided or no element with the provided .parentId was found. CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, // An element was declared that using CLAY_SIZING_PERCENT but the percentage value was over 1. Percentage values are expected to be in the 0-1 range. CLAY_ERROR_TYPE_PERCENTAGE_OVER_1, // Clay encountered an internal error. It would be wonderful if you could report this so we can fix it! CLAY_ERROR_TYPE_INTERNAL_ERROR, // Clay__OpenElement was called more times than Clay__CloseElement, so there were still remaining open elements when the layout ended. CLAY_ERROR_TYPE_UNBALANCED_OPEN_CLOSE, } Clay_ErrorType; // Data to identify the error that clay has encountered. typedef struct Clay_ErrorData { // Represents the type of error clay encountered while computing layout. // CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED - A text measurement function wasn't provided using Clay_SetMeasureTextFunction(), or the provided function was null. // CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED - Clay attempted to allocate its internal data structures but ran out of space. The arena passed to Clay_Initialize was created with a capacity smaller than that required by Clay_MinMemorySize(). // CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED - Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxElementCount(). // CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED - Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxMeasureTextCacheWordCount(). // CLAY_ERROR_TYPE_DUPLICATE_ID - Two elements were declared with exactly the same ID within one layout. // CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND - A floating element was declared using CLAY_ATTACH_TO_ELEMENT_ID and either an invalid .parentId was provided or no element with the provided .parentId was found. // CLAY_ERROR_TYPE_PERCENTAGE_OVER_1 - An element was declared that using CLAY_SIZING_PERCENT but the percentage value was over 1. Percentage values are expected to be in the 0-1 range. // CLAY_ERROR_TYPE_INTERNAL_ERROR - Clay encountered an internal error. It would be wonderful if you could report this so we can fix it! Clay_ErrorType errorType; // A string containing human-readable error text that explains the error in more detail. Clay_String errorText; // A transparent pointer passed through from when the error handler was first provided. void *userData; } Clay_ErrorData; // A wrapper struct around Clay's error handler function. typedef struct { // A user provided function to call when Clay encounters an error during layout. void (*errorHandlerFunction)(Clay_ErrorData errorText); // A pointer that will be transparently passed through to the error handler when it is called. void *userData; } Clay_ErrorHandler; // Function Forward Declarations --------------------------------- // Public API functions ------------------------------------------ // Returns the size, in bytes, of the minimum amount of memory Clay requires to operate at its current settings. CLAY_DLL_EXPORT uint32_t Clay_MinMemorySize(void); // Creates an arena for clay to use for its internal allocations, given a certain capacity in bytes and a pointer to an allocation of at least that size. // Intended to be used with Clay_MinMemorySize in the following way: // uint32_t minMemoryRequired = Clay_MinMemorySize(); // Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(minMemoryRequired, malloc(minMemoryRequired)); CLAY_DLL_EXPORT Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory); // Sets the state of the "pointer" (i.e. the mouse or touch) in Clay's internal data. Used for detecting and responding to mouse events in the debug view, // as well as for Clay_Hovered() and scroll element handling. CLAY_DLL_EXPORT void Clay_SetPointerState(Clay_Vector2 position, bool pointerDown); // Initialize Clay's internal arena and setup required data before layout can begin. Only needs to be called once. // - arena can be created using Clay_CreateArenaWithCapacityAndMemory() // - layoutDimensions are the initial bounding dimensions of the layout (i.e. the screen width and height for a full screen layout) // - errorHandler is used by Clay to inform you if something has gone wrong in configuration or layout. CLAY_DLL_EXPORT Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler); // Returns the Context that clay is currently using. Used when using multiple instances of clay simultaneously. CLAY_DLL_EXPORT Clay_Context* Clay_GetCurrentContext(void); // Sets the context that clay will use to compute the layout. // Used to restore a context saved from Clay_GetCurrentContext when using multiple instances of clay simultaneously. CLAY_DLL_EXPORT void Clay_SetCurrentContext(Clay_Context* context); // Updates the state of Clay's internal scroll data, updating scroll content positions if scrollDelta is non zero, and progressing momentum scrolling. // - enableDragScrolling when set to true will enable mobile device like "touch drag" scroll of scroll containers, including momentum scrolling after the touch has ended. // - scrollDelta is the amount to scroll this frame on each axis in pixels. // - deltaTime is the time in seconds since the last "frame" (scroll update) CLAY_DLL_EXPORT void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime); // Returns the internally stored scroll offset for the currently open element. // Generally intended for use with clip elements to create scrolling containers. CLAY_DLL_EXPORT Clay_Vector2 Clay_GetScrollOffset(void); // Updates the layout dimensions in response to the window or outer container being resized. CLAY_DLL_EXPORT void Clay_SetLayoutDimensions(Clay_Dimensions dimensions); // Called before starting any layout declarations. CLAY_DLL_EXPORT void Clay_BeginLayout(void); // Called when all layout declarations are finished. // Computes the layout and generates and returns the array of render commands to draw. CLAY_DLL_EXPORT Clay_RenderCommandArray Clay_EndLayout(void); // Calculates a hash ID from the given idString. // Generally only used for dynamic strings when CLAY_ID("stringLiteral") can't be used. CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementId(Clay_String idString); // Calculates a hash ID from the given idString and index. // - index is used to avoid constructing dynamic ID strings in loops. // Generally only used for dynamic strings when CLAY_IDI("stringLiteral", index) can't be used. CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index); // Returns layout data such as the final calculated bounding box for an element with a given ID. // The returned Clay_ElementData contains a `found` bool that will be true if an element with the provided ID was found. // This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings. CLAY_DLL_EXPORT Clay_ElementData Clay_GetElementData(Clay_ElementId id); // Returns true if the pointer position provided by Clay_SetPointerState is within the current element's bounding box. // Works during element declaration, e.g. CLAY({ .backgroundColor = Clay_Hovered() ? BLUE : RED }); CLAY_DLL_EXPORT bool Clay_Hovered(void); // Bind a callback that will be called when the pointer position provided by Clay_SetPointerState is within the current element's bounding box. // - onHoverFunction is a function pointer to a user defined function. // - userData is a pointer that will be transparently passed through when the onHoverFunction is called. CLAY_DLL_EXPORT void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData), void *userData); // An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box. // This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings. CLAY_DLL_EXPORT bool Clay_PointerOver(Clay_ElementId elementId); // Returns the array of element IDs that the pointer is currently over. CLAY_DLL_EXPORT Clay_ElementIdArray Clay_GetPointerOverIds(void); // Returns data representing the state of the scrolling element with the provided ID. // The returned Clay_ScrollContainerData contains a `found` bool that will be true if a scroll element was found with the provided ID. // An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box. // This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings. CLAY_DLL_EXPORT Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id); // Binds a callback function that Clay will call to determine the dimensions of a given string slice. // - measureTextFunction is a user provided function that adheres to the interface Clay_Dimensions (Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); // - userData is a pointer that will be transparently passed through when the measureTextFunction is called. CLAY_DLL_EXPORT void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData); // Experimental - Used in cases where Clay needs to integrate with a system that manages its own scrolling containers externally. // Please reach out if you plan to use this function, as it may be subject to change. CLAY_DLL_EXPORT void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData); // A bounds-checked "get" function for the Clay_RenderCommandArray returned from Clay_EndLayout(). CLAY_DLL_EXPORT Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index); // Enables and disables Clay's internal debug tools. // This state is retained and does not need to be set each frame. CLAY_DLL_EXPORT void Clay_SetDebugModeEnabled(bool enabled); // Returns true if Clay's internal debug tools are currently enabled. CLAY_DLL_EXPORT bool Clay_IsDebugModeEnabled(void); // Enables and disables visibility culling. By default, Clay will not generate render commands for elements whose bounding box is entirely outside the screen. CLAY_DLL_EXPORT void Clay_SetCullingEnabled(bool enabled); // Returns the maximum number of UI elements supported by Clay's current configuration. CLAY_DLL_EXPORT int32_t Clay_GetMaxElementCount(void); // Modifies the maximum number of UI elements supported by Clay's current configuration. // This may require reallocating additional memory, and re-calling Clay_Initialize(); CLAY_DLL_EXPORT void Clay_SetMaxElementCount(int32_t maxElementCount); // Returns the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache. CLAY_DLL_EXPORT int32_t Clay_GetMaxMeasureTextCacheWordCount(void); // Modifies the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache. // This may require reallocating additional memory, and re-calling Clay_Initialize(); CLAY_DLL_EXPORT void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount); // Resets Clay's internal text measurement cache. Useful if font mappings have changed or fonts have been reloaded. CLAY_DLL_EXPORT void Clay_ResetMeasureTextCache(void); // Internal API functions required by macros ---------------------- CLAY_DLL_EXPORT void Clay__OpenElement(void); CLAY_DLL_EXPORT void Clay__OpenElementWithId(Clay_ElementId elementId); CLAY_DLL_EXPORT void Clay__ConfigureOpenElement(const Clay_ElementDeclaration config); CLAY_DLL_EXPORT void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *config); CLAY_DLL_EXPORT void Clay__CloseElement(void); CLAY_DLL_EXPORT Clay_ElementId Clay__HashString(Clay_String key, uint32_t seed); CLAY_DLL_EXPORT Clay_ElementId Clay__HashStringWithOffset(Clay_String key, uint32_t offset, uint32_t seed); CLAY_DLL_EXPORT void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig); CLAY_DLL_EXPORT Clay_TextElementConfig *Clay__StoreTextElementConfig(Clay_TextElementConfig config); CLAY_DLL_EXPORT uint32_t Clay__GetParentElementId(void); extern Clay_Color Clay__debugViewHighlightColor; extern uint32_t Clay__debugViewWidth; #ifdef __cplusplus } #endif #endif // CLAY_HEADER // ----------------------------------------- // IMPLEMENTATION -------------------------- // ----------------------------------------- #ifdef CLAY_IMPLEMENTATION #undef CLAY_IMPLEMENTATION #ifndef CLAY__NULL #define CLAY__NULL 0 #endif #ifndef CLAY__MAXFLOAT #define CLAY__MAXFLOAT 3.40282346638528859812e+38F #endif Clay_LayoutConfig CLAY_LAYOUT_DEFAULT = CLAY__DEFAULT_STRUCT; Clay_Color Clay__Color_DEFAULT = CLAY__DEFAULT_STRUCT; Clay_CornerRadius Clay__CornerRadius_DEFAULT = CLAY__DEFAULT_STRUCT; Clay_BorderWidth Clay__BorderWidth_DEFAULT = CLAY__DEFAULT_STRUCT; // The below functions define array bounds checking and convenience functions for a provided type. #define CLAY__ARRAY_DEFINE_FUNCTIONS(typeName, arrayName) \ \ typedef struct \ { \ int32_t length; \ typeName *internalArray; \ } arrayName##Slice; \ \ typeName typeName##_DEFAULT = CLAY__DEFAULT_STRUCT; \ \ arrayName arrayName##_Allocate_Arena(int32_t capacity, Clay_Arena *arena) { \ return CLAY__INIT(arrayName){.capacity = capacity, .length = 0, \ .internalArray = (typeName *)Clay__Array_Allocate_Arena(capacity, sizeof(typeName), arena)}; \ } \ \ typeName *arrayName##_Get(arrayName *array, int32_t index) { \ return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &typeName##_DEFAULT; \ } \ \ typeName arrayName##_GetValue(arrayName *array, int32_t index) { \ return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : typeName##_DEFAULT; \ } \ \ typeName *arrayName##_Add(arrayName *array, typeName item) { \ if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { \ array->internalArray[array->length++] = item; \ return &array->internalArray[array->length - 1]; \ } \ return &typeName##_DEFAULT; \ } \ \ typeName *arrayName##Slice_Get(arrayName##Slice *slice, int32_t index) { \ return Clay__Array_RangeCheck(index, slice->length) ? &slice->internalArray[index] : &typeName##_DEFAULT; \ } \ \ typeName arrayName##_RemoveSwapback(arrayName *array, int32_t index) { \ if (Clay__Array_RangeCheck(index, array->length)) { \ array->length--; \ typeName removed = array->internalArray[index]; \ array->internalArray[index] = array->internalArray[array->length]; \ return removed; \ } \ return typeName##_DEFAULT; \ } \ \ void arrayName##_Set(arrayName *array, int32_t index, typeName value) { \ if (Clay__Array_RangeCheck(index, array->capacity)) { \ array->internalArray[index] = value; \ array->length = index < array->length ? array->length : index + 1; \ } \ } \ #define CLAY__ARRAY_DEFINE(typeName, arrayName) \ typedef struct \ { \ int32_t capacity; \ int32_t length; \ typeName *internalArray; \ } arrayName; \ \ CLAY__ARRAY_DEFINE_FUNCTIONS(typeName, arrayName) \ Clay_Context *Clay__currentContext; int32_t Clay__defaultMaxElementCount = 8192; int32_t Clay__defaultMaxMeasureTextWordCacheCount = 16384; void Clay__ErrorHandlerFunctionDefault(Clay_ErrorData errorText) { (void) errorText; } Clay_String CLAY__SPACECHAR = { .length = 1, .chars = " " }; Clay_String CLAY__STRING_DEFAULT = { .length = 0, .chars = NULL }; typedef struct { bool maxElementsExceeded; bool maxRenderCommandsExceeded; bool maxTextMeasureCacheExceeded; bool textMeasurementFunctionNotSet; } Clay_BooleanWarnings; typedef struct { Clay_String baseMessage; Clay_String dynamicMessage; } Clay__Warning; Clay__Warning CLAY__WARNING_DEFAULT = CLAY__DEFAULT_STRUCT; typedef struct { int32_t capacity; int32_t length; Clay__Warning *internalArray; } Clay__WarningArray; typedef struct { Clay_Color backgroundColor; Clay_CornerRadius cornerRadius; void* userData; } Clay_SharedElementConfig; CLAY__WRAPPER_STRUCT(Clay_SharedElementConfig); Clay__WarningArray Clay__WarningArray_Allocate_Arena(int32_t capacity, Clay_Arena *arena); Clay__Warning *Clay__WarningArray_Add(Clay__WarningArray *array, Clay__Warning item); void* Clay__Array_Allocate_Arena(int32_t capacity, uint32_t itemSize, Clay_Arena *arena); bool Clay__Array_RangeCheck(int32_t index, int32_t length); bool Clay__Array_AddCapacityCheck(int32_t length, int32_t capacity); CLAY__ARRAY_DEFINE(bool, Clay__boolArray) CLAY__ARRAY_DEFINE(int32_t, Clay__int32_tArray) CLAY__ARRAY_DEFINE(char, Clay__charArray) CLAY__ARRAY_DEFINE_FUNCTIONS(Clay_ElementId, Clay_ElementIdArray) CLAY__ARRAY_DEFINE(Clay_LayoutConfig, Clay__LayoutConfigArray) CLAY__ARRAY_DEFINE(Clay_TextElementConfig, Clay__TextElementConfigArray) CLAY__ARRAY_DEFINE(Clay_AspectRatioElementConfig, Clay__AspectRatioElementConfigArray) CLAY__ARRAY_DEFINE(Clay_ImageElementConfig, Clay__ImageElementConfigArray) CLAY__ARRAY_DEFINE(Clay_FloatingElementConfig, Clay__FloatingElementConfigArray) CLAY__ARRAY_DEFINE(Clay_CustomElementConfig, Clay__CustomElementConfigArray) CLAY__ARRAY_DEFINE(Clay_ClipElementConfig, Clay__ClipElementConfigArray) CLAY__ARRAY_DEFINE(Clay_BorderElementConfig, Clay__BorderElementConfigArray) CLAY__ARRAY_DEFINE(Clay_String, Clay__StringArray) CLAY__ARRAY_DEFINE(Clay_SharedElementConfig, Clay__SharedElementConfigArray) CLAY__ARRAY_DEFINE_FUNCTIONS(Clay_RenderCommand, Clay_RenderCommandArray) typedef CLAY_PACKED_ENUM { CLAY__ELEMENT_CONFIG_TYPE_NONE, CLAY__ELEMENT_CONFIG_TYPE_BORDER, CLAY__ELEMENT_CONFIG_TYPE_FLOATING, CLAY__ELEMENT_CONFIG_TYPE_CLIP, CLAY__ELEMENT_CONFIG_TYPE_ASPECT, CLAY__ELEMENT_CONFIG_TYPE_IMAGE, CLAY__ELEMENT_CONFIG_TYPE_TEXT, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM, CLAY__ELEMENT_CONFIG_TYPE_SHARED, } Clay__ElementConfigType; typedef union { Clay_TextElementConfig *textElementConfig; Clay_AspectRatioElementConfig *aspectRatioElementConfig; Clay_ImageElementConfig *imageElementConfig; Clay_FloatingElementConfig *floatingElementConfig; Clay_CustomElementConfig *customElementConfig; Clay_ClipElementConfig *clipElementConfig; Clay_BorderElementConfig *borderElementConfig; Clay_SharedElementConfig *sharedElementConfig; } Clay_ElementConfigUnion; typedef struct { Clay__ElementConfigType type; Clay_ElementConfigUnion config; } Clay_ElementConfig; CLAY__ARRAY_DEFINE(Clay_ElementConfig, Clay__ElementConfigArray) typedef struct { Clay_Dimensions dimensions; Clay_String line; } Clay__WrappedTextLine; CLAY__ARRAY_DEFINE(Clay__WrappedTextLine, Clay__WrappedTextLineArray) typedef struct { Clay_String text; Clay_Dimensions preferredDimensions; int32_t elementIndex; Clay__WrappedTextLineArraySlice wrappedLines; } Clay__TextElementData; CLAY__ARRAY_DEFINE(Clay__TextElementData, Clay__TextElementDataArray) typedef struct { int32_t *elements; uint16_t length; } Clay__LayoutElementChildren; typedef struct { union { Clay__LayoutElementChildren children; Clay__TextElementData *textElementData; } childrenOrTextContent; Clay_Dimensions dimensions; Clay_Dimensions minDimensions; Clay_LayoutConfig *layoutConfig; Clay__ElementConfigArraySlice elementConfigs; uint32_t id; uint16_t floatingChildrenCount; } Clay_LayoutElement; CLAY__ARRAY_DEFINE(Clay_LayoutElement, Clay_LayoutElementArray) typedef struct { Clay_LayoutElement *layoutElement; Clay_BoundingBox boundingBox; Clay_Dimensions contentSize; Clay_Vector2 scrollOrigin; Clay_Vector2 pointerOrigin; Clay_Vector2 scrollMomentum; Clay_Vector2 scrollPosition; Clay_Vector2 previousDelta; float momentumTime; uint32_t elementId; bool openThisFrame; bool pointerScrollActive; } Clay__ScrollContainerDataInternal; CLAY__ARRAY_DEFINE(Clay__ScrollContainerDataInternal, Clay__ScrollContainerDataInternalArray) typedef struct { bool collision; bool collapsed; } Clay__DebugElementData; CLAY__ARRAY_DEFINE(Clay__DebugElementData, Clay__DebugElementDataArray) typedef struct { // todo get this struct into a single cache line Clay_BoundingBox boundingBox; Clay_ElementId elementId; Clay_LayoutElement* layoutElement; void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData); void *hoverFunctionUserData; int32_t nextIndex; uint32_t generation; Clay__DebugElementData *debugData; } Clay_LayoutElementHashMapItem; CLAY__ARRAY_DEFINE(Clay_LayoutElementHashMapItem, Clay__LayoutElementHashMapItemArray) typedef struct { int32_t startOffset; int32_t length; float width; int32_t next; } Clay__MeasuredWord; CLAY__ARRAY_DEFINE(Clay__MeasuredWord, Clay__MeasuredWordArray) typedef struct { Clay_Dimensions unwrappedDimensions; int32_t measuredWordsStartIndex; float minWidth; bool containsNewlines; // Hash map data uint32_t id; int32_t nextIndex; uint32_t generation; } Clay__MeasureTextCacheItem; CLAY__ARRAY_DEFINE(Clay__MeasureTextCacheItem, Clay__MeasureTextCacheItemArray) typedef struct { Clay_LayoutElement *layoutElement; Clay_Vector2 position; Clay_Vector2 nextChildOffset; } Clay__LayoutElementTreeNode; CLAY__ARRAY_DEFINE(Clay__LayoutElementTreeNode, Clay__LayoutElementTreeNodeArray) typedef struct { int32_t layoutElementIndex; uint32_t parentId; // This can be zero in the case of the root layout tree uint32_t clipElementId; // This can be zero if there is no clip element int16_t zIndex; Clay_Vector2 pointerOffset; // Only used when scroll containers are managed externally } Clay__LayoutElementTreeRoot; CLAY__ARRAY_DEFINE(Clay__LayoutElementTreeRoot, Clay__LayoutElementTreeRootArray) struct Clay_Context { int32_t maxElementCount; int32_t maxMeasureTextCacheWordCount; bool warningsEnabled; Clay_ErrorHandler errorHandler; Clay_BooleanWarnings booleanWarnings; Clay__WarningArray warnings; Clay_PointerData pointerInfo; Clay_Dimensions layoutDimensions; Clay_ElementId dynamicElementIndexBaseHash; uint32_t dynamicElementIndex; bool debugModeEnabled; bool disableCulling; bool externalScrollHandlingEnabled; uint32_t debugSelectedElementId; uint32_t generation; uintptr_t arenaResetOffset; void *measureTextUserData; void *queryScrollOffsetUserData; Clay_Arena internalArena; // Layout Elements / Render Commands Clay_LayoutElementArray layoutElements; Clay_RenderCommandArray renderCommands; Clay__int32_tArray openLayoutElementStack; Clay__int32_tArray layoutElementChildren; Clay__int32_tArray layoutElementChildrenBuffer; Clay__TextElementDataArray textElementData; Clay__int32_tArray aspectRatioElementIndexes; Clay__int32_tArray reusableElementIndexBuffer; Clay__int32_tArray layoutElementClipElementIds; // Configs Clay__LayoutConfigArray layoutConfigs; Clay__ElementConfigArray elementConfigs; Clay__TextElementConfigArray textElementConfigs; Clay__AspectRatioElementConfigArray aspectRatioElementConfigs; Clay__ImageElementConfigArray imageElementConfigs; Clay__FloatingElementConfigArray floatingElementConfigs; Clay__ClipElementConfigArray clipElementConfigs; Clay__CustomElementConfigArray customElementConfigs; Clay__BorderElementConfigArray borderElementConfigs; Clay__SharedElementConfigArray sharedElementConfigs; // Misc Data Structures Clay__StringArray layoutElementIdStrings; Clay__WrappedTextLineArray wrappedTextLines; Clay__LayoutElementTreeNodeArray layoutElementTreeNodeArray1; Clay__LayoutElementTreeRootArray layoutElementTreeRoots; Clay__LayoutElementHashMapItemArray layoutElementsHashMapInternal; Clay__int32_tArray layoutElementsHashMap; Clay__MeasureTextCacheItemArray measureTextHashMapInternal; Clay__int32_tArray measureTextHashMapInternalFreeList; Clay__int32_tArray measureTextHashMap; Clay__MeasuredWordArray measuredWords; Clay__int32_tArray measuredWordsFreeList; Clay__int32_tArray openClipElementStack; Clay_ElementIdArray pointerOverIds; Clay__ScrollContainerDataInternalArray scrollContainerDatas; Clay__boolArray treeNodeVisited; Clay__charArray dynamicStringData; Clay__DebugElementDataArray debugElementData; }; Clay_Context* Clay__Context_Allocate_Arena(Clay_Arena *arena) { size_t totalSizeBytes = sizeof(Clay_Context); if (totalSizeBytes > arena->capacity) { return NULL; } arena->nextAllocation += totalSizeBytes; return (Clay_Context*)(arena->memory); } Clay_String Clay__WriteStringToCharBuffer(Clay__charArray *buffer, Clay_String string) { for (int32_t i = 0; i < string.length; i++) { buffer->internalArray[buffer->length + i] = string.chars[i]; } buffer->length += string.length; return CLAY__INIT(Clay_String) { .length = string.length, .chars = (const char *)(buffer->internalArray + buffer->length - string.length) }; } #ifdef CLAY_WASM __attribute__((import_module("clay"), import_name("measureTextFunction"))) Clay_Dimensions Clay__MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); __attribute__((import_module("clay"), import_name("queryScrollOffsetFunction"))) Clay_Vector2 Clay__QueryScrollOffset(uint32_t elementId, void *userData); #else Clay_Dimensions (*Clay__MeasureText)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); Clay_Vector2 (*Clay__QueryScrollOffset)(uint32_t elementId, void *userData); #endif Clay_LayoutElement* Clay__GetOpenLayoutElement(void) { Clay_Context* context = Clay_GetCurrentContext(); return Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1)); } uint32_t Clay__GetParentElementId(void) { Clay_Context* context = Clay_GetCurrentContext(); return Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2))->id; } Clay_LayoutConfig * Clay__StoreLayoutConfig(Clay_LayoutConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &CLAY_LAYOUT_DEFAULT : Clay__LayoutConfigArray_Add(&Clay_GetCurrentContext()->layoutConfigs, config); } Clay_TextElementConfig * Clay__StoreTextElementConfig(Clay_TextElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_TextElementConfig_DEFAULT : Clay__TextElementConfigArray_Add(&Clay_GetCurrentContext()->textElementConfigs, config); } Clay_AspectRatioElementConfig * Clay__StoreAspectRatioElementConfig(Clay_AspectRatioElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_AspectRatioElementConfig_DEFAULT : Clay__AspectRatioElementConfigArray_Add(&Clay_GetCurrentContext()->aspectRatioElementConfigs, config); } Clay_ImageElementConfig * Clay__StoreImageElementConfig(Clay_ImageElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_ImageElementConfig_DEFAULT : Clay__ImageElementConfigArray_Add(&Clay_GetCurrentContext()->imageElementConfigs, config); } Clay_FloatingElementConfig * Clay__StoreFloatingElementConfig(Clay_FloatingElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_FloatingElementConfig_DEFAULT : Clay__FloatingElementConfigArray_Add(&Clay_GetCurrentContext()->floatingElementConfigs, config); } Clay_CustomElementConfig * Clay__StoreCustomElementConfig(Clay_CustomElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_CustomElementConfig_DEFAULT : Clay__CustomElementConfigArray_Add(&Clay_GetCurrentContext()->customElementConfigs, config); } Clay_ClipElementConfig * Clay__StoreClipElementConfig(Clay_ClipElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_ClipElementConfig_DEFAULT : Clay__ClipElementConfigArray_Add(&Clay_GetCurrentContext()->clipElementConfigs, config); } Clay_BorderElementConfig * Clay__StoreBorderElementConfig(Clay_BorderElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_BorderElementConfig_DEFAULT : Clay__BorderElementConfigArray_Add(&Clay_GetCurrentContext()->borderElementConfigs, config); } Clay_SharedElementConfig * Clay__StoreSharedElementConfig(Clay_SharedElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_SharedElementConfig_DEFAULT : Clay__SharedElementConfigArray_Add(&Clay_GetCurrentContext()->sharedElementConfigs, config); } Clay_ElementConfig Clay__AttachElementConfig(Clay_ElementConfigUnion config, Clay__ElementConfigType type) { Clay_Context* context = Clay_GetCurrentContext(); if (context->booleanWarnings.maxElementsExceeded) { return CLAY__INIT(Clay_ElementConfig) CLAY__DEFAULT_STRUCT; } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); openLayoutElement->elementConfigs.length++; return *Clay__ElementConfigArray_Add(&context->elementConfigs, CLAY__INIT(Clay_ElementConfig) { .type = type, .config = config }); } Clay_ElementConfigUnion Clay__FindElementConfigWithType(Clay_LayoutElement *element, Clay__ElementConfigType type) { for (int32_t i = 0; i < element->elementConfigs.length; i++) { Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&element->elementConfigs, i); if (config->type == type) { return config->config; } } return CLAY__INIT(Clay_ElementConfigUnion) { NULL }; } Clay_ElementId Clay__HashNumber(const uint32_t offset, const uint32_t seed) { uint32_t hash = seed; hash += (offset + 48); hash += (hash << 10); hash ^= (hash >> 6); hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = seed, .stringId = CLAY__STRING_DEFAULT }; // Reserve the hash result of zero as "null id" } Clay_ElementId Clay__HashString(Clay_String key, const uint32_t seed) { uint32_t hash = seed; for (int32_t i = 0; i < key.length; i++) { hash += key.chars[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = 0, .baseId = hash + 1, .stringId = key }; // Reserve the hash result of zero as "null id" } Clay_ElementId Clay__HashStringWithOffset(Clay_String key, const uint32_t offset, const uint32_t seed) { uint32_t hash = 0; uint32_t base = seed; for (int32_t i = 0; i < key.length; i++) { base += key.chars[i]; base += (base << 10); base ^= (base >> 6); } hash = base; hash += offset; hash += (hash << 10); hash ^= (hash >> 6); hash += (hash << 3); base += (base << 3); hash ^= (hash >> 11); base ^= (base >> 11); hash += (hash << 15); base += (base << 15); return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id" } #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) static inline __m128i Clay__SIMDRotateLeft(__m128i x, int r) { return _mm_or_si128(_mm_slli_epi64(x, r), _mm_srli_epi64(x, 64 - r)); } static inline void Clay__SIMDARXMix(__m128i* a, __m128i* b) { *a = _mm_add_epi64(*a, *b); *b = _mm_xor_si128(Clay__SIMDRotateLeft(*b, 17), *a); } uint64_t Clay__HashData(const uint8_t* data, size_t length) { // Pinched these constants from the BLAKE implementation __m128i v0 = _mm_set1_epi64x(0x6a09e667f3bcc908ULL); __m128i v1 = _mm_set1_epi64x(0xbb67ae8584caa73bULL); __m128i v2 = _mm_set1_epi64x(0x3c6ef372fe94f82bULL); __m128i v3 = _mm_set1_epi64x(0xa54ff53a5f1d36f1ULL); uint8_t overflowBuffer[16] = { 0 }; // Temporary buffer for small inputs while (length > 0) { __m128i msg; if (length >= 16) { msg = _mm_loadu_si128((const __m128i*)data); data += 16; length -= 16; } else { for (size_t i = 0; i < length; i++) { overflowBuffer[i] = data[i]; } msg = _mm_loadu_si128((const __m128i*)overflowBuffer); length = 0; } v0 = _mm_xor_si128(v0, msg); Clay__SIMDARXMix(&v0, &v1); Clay__SIMDARXMix(&v2, &v3); v0 = _mm_add_epi64(v0, v2); v1 = _mm_add_epi64(v1, v3); } Clay__SIMDARXMix(&v0, &v1); Clay__SIMDARXMix(&v2, &v3); v0 = _mm_add_epi64(v0, v2); v1 = _mm_add_epi64(v1, v3); v0 = _mm_add_epi64(v0, v1); uint64_t result[2]; _mm_storeu_si128((__m128i*)result, v0); return result[0] ^ result[1]; } #elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) static inline void Clay__SIMDARXMix(uint64x2_t* a, uint64x2_t* b) { *a = vaddq_u64(*a, *b); *b = veorq_u64(vorrq_u64(vshlq_n_u64(*b, 17), vshrq_n_u64(*b, 64 - 17)), *a); } uint64_t Clay__HashData(const uint8_t* data, size_t length) { // Pinched these constants from the BLAKE implementation uint64x2_t v0 = vdupq_n_u64(0x6a09e667f3bcc908ULL); uint64x2_t v1 = vdupq_n_u64(0xbb67ae8584caa73bULL); uint64x2_t v2 = vdupq_n_u64(0x3c6ef372fe94f82bULL); uint64x2_t v3 = vdupq_n_u64(0xa54ff53a5f1d36f1ULL); uint8_t overflowBuffer[8] = { 0 }; while (length > 0) { uint64x2_t msg; if (length > 16) { msg = vld1q_u64((const uint64_t*)data); data += 16; length -= 16; } else if (length > 8) { msg = vcombine_u64(vld1_u64((const uint64_t*)data), vdup_n_u64(0)); data += 8; length -= 8; } else { for (size_t i = 0; i < length; i++) { overflowBuffer[i] = data[i]; } uint8x8_t lower = vld1_u8(overflowBuffer); msg = vreinterpretq_u64_u8(vcombine_u8(lower, vdup_n_u8(0))); length = 0; } v0 = veorq_u64(v0, msg); Clay__SIMDARXMix(&v0, &v1); Clay__SIMDARXMix(&v2, &v3); v0 = vaddq_u64(v0, v2); v1 = vaddq_u64(v1, v3); } Clay__SIMDARXMix(&v0, &v1); Clay__SIMDARXMix(&v2, &v3); v0 = vaddq_u64(v0, v2); v1 = vaddq_u64(v1, v3); v0 = vaddq_u64(v0, v1); uint64_t result[2]; vst1q_u64(result, v0); return result[0] ^ result[1]; } #else uint64_t Clay__HashData(const uint8_t* data, size_t length) { uint64_t hash = 0; for (size_t i = 0; i < length; i++) { hash += data[i]; hash += (hash << 10); hash ^= (hash >> 6); } return hash; } #endif uint32_t Clay__HashStringContentsWithConfig(Clay_String *text, Clay_TextElementConfig *config) { uint32_t hash = 0; if (text->isStaticallyAllocated) { hash += (uintptr_t)text->chars; hash += (hash << 10); hash ^= (hash >> 6); hash += text->length; hash += (hash << 10); hash ^= (hash >> 6); } else { hash = Clay__HashData((const uint8_t *)text->chars, text->length) % UINT32_MAX; } hash += config->fontId; hash += (hash << 10); hash ^= (hash >> 6); hash += config->fontSize; hash += (hash << 10); hash ^= (hash >> 6); hash += config->letterSpacing; hash += (hash << 10); hash ^= (hash >> 6); hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash + 1; // Reserve the hash result of zero as "null id" } Clay__MeasuredWord *Clay__AddMeasuredWord(Clay__MeasuredWord word, Clay__MeasuredWord *previousWord) { Clay_Context* context = Clay_GetCurrentContext(); if (context->measuredWordsFreeList.length > 0) { uint32_t newItemIndex = Clay__int32_tArray_GetValue(&context->measuredWordsFreeList, (int)context->measuredWordsFreeList.length - 1); context->measuredWordsFreeList.length--; Clay__MeasuredWordArray_Set(&context->measuredWords, (int)newItemIndex, word); previousWord->next = (int32_t)newItemIndex; return Clay__MeasuredWordArray_Get(&context->measuredWords, (int)newItemIndex); } else { previousWord->next = (int32_t)context->measuredWords.length; return Clay__MeasuredWordArray_Add(&context->measuredWords, word); } } Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_TextElementConfig *config) { Clay_Context* context = Clay_GetCurrentContext(); #ifndef CLAY_WASM if (!Clay__MeasureText) { if (!context->booleanWarnings.textMeasurementFunctionNotSet) { context->booleanWarnings.textMeasurementFunctionNotSet = true; context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, .errorText = CLAY_STRING("Clay's internal MeasureText function is null. You may have forgotten to call Clay_SetMeasureTextFunction(), or passed a NULL function pointer by mistake."), .userData = context->errorHandler.userData }); } return &Clay__MeasureTextCacheItem_DEFAULT; } #endif uint32_t id = Clay__HashStringContentsWithConfig(text, config); uint32_t hashBucket = id % (context->maxMeasureTextCacheWordCount / 32); int32_t elementIndexPrevious = 0; int32_t elementIndex = context->measureTextHashMap.internalArray[hashBucket]; while (elementIndex != 0) { Clay__MeasureTextCacheItem *hashEntry = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, elementIndex); if (hashEntry->id == id) { hashEntry->generation = context->generation; return hashEntry; } // This element hasn't been seen in a few frames, delete the hash map item if (context->generation - hashEntry->generation > 2) { // Add all the measured words that were included in this measurement to the freelist int32_t nextWordIndex = hashEntry->measuredWordsStartIndex; while (nextWordIndex != -1) { Clay__MeasuredWord *measuredWord = Clay__MeasuredWordArray_Get(&context->measuredWords, nextWordIndex); Clay__int32_tArray_Add(&context->measuredWordsFreeList, nextWordIndex); nextWordIndex = measuredWord->next; } int32_t nextIndex = hashEntry->nextIndex; Clay__MeasureTextCacheItemArray_Set(&context->measureTextHashMapInternal, elementIndex, CLAY__INIT(Clay__MeasureTextCacheItem) { .measuredWordsStartIndex = -1 }); Clay__int32_tArray_Add(&context->measureTextHashMapInternalFreeList, elementIndex); if (elementIndexPrevious == 0) { context->measureTextHashMap.internalArray[hashBucket] = nextIndex; } else { Clay__MeasureTextCacheItem *previousHashEntry = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, elementIndexPrevious); previousHashEntry->nextIndex = nextIndex; } elementIndex = nextIndex; } else { elementIndexPrevious = elementIndex; elementIndex = hashEntry->nextIndex; } } int32_t newItemIndex = 0; Clay__MeasureTextCacheItem newCacheItem = { .measuredWordsStartIndex = -1, .id = id, .generation = context->generation }; Clay__MeasureTextCacheItem *measured = NULL; if (context->measureTextHashMapInternalFreeList.length > 0) { newItemIndex = Clay__int32_tArray_GetValue(&context->measureTextHashMapInternalFreeList, context->measureTextHashMapInternalFreeList.length - 1); context->measureTextHashMapInternalFreeList.length--; Clay__MeasureTextCacheItemArray_Set(&context->measureTextHashMapInternal, newItemIndex, newCacheItem); measured = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, newItemIndex); } else { if (context->measureTextHashMapInternal.length == context->measureTextHashMapInternal.capacity - 1) { if (!context->booleanWarnings.maxTextMeasureCacheExceeded) { context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, .errorText = CLAY_STRING("Clay ran out of capacity while attempting to measure text elements. Try using Clay_SetMaxElementCount() with a higher value."), .userData = context->errorHandler.userData }); context->booleanWarnings.maxTextMeasureCacheExceeded = true; } return &Clay__MeasureTextCacheItem_DEFAULT; } measured = Clay__MeasureTextCacheItemArray_Add(&context->measureTextHashMapInternal, newCacheItem); newItemIndex = context->measureTextHashMapInternal.length - 1; } int32_t start = 0; int32_t end = 0; float lineWidth = 0; float measuredWidth = 0; float measuredHeight = 0; float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, config, context->measureTextUserData).width; Clay__MeasuredWord tempWord = { .next = -1 }; Clay__MeasuredWord *previousWord = &tempWord; while (end < text->length) { if (context->measuredWords.length == context->measuredWords.capacity - 1) { if (!context->booleanWarnings.maxTextMeasureCacheExceeded) { context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, .errorText = CLAY_STRING("Clay has run out of space in it's internal text measurement cache. Try using Clay_SetMaxMeasureTextCacheWordCount() (default 16384, with 1 unit storing 1 measured word)."), .userData = context->errorHandler.userData }); context->booleanWarnings.maxTextMeasureCacheExceeded = true; } return &Clay__MeasureTextCacheItem_DEFAULT; } char current = text->chars[end]; if (current == ' ' || current == '\n') { int32_t length = end - start; Clay_Dimensions dimensions = CLAY__DEFAULT_STRUCT; if (length > 0) { dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) {.length = length, .chars = &text->chars[start], .baseChars = text->chars}, config, context->measureTextUserData); } measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth); measuredHeight = CLAY__MAX(measuredHeight, dimensions.height); if (current == ' ') { dimensions.width += spaceWidth; previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length + 1, .width = dimensions.width, .next = -1 }, previousWord); lineWidth += dimensions.width; } if (current == '\n') { if (length > 0) { previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length, .width = dimensions.width, .next = -1 }, previousWord); } previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = end + 1, .length = 0, .width = 0, .next = -1 }, previousWord); lineWidth += dimensions.width; measuredWidth = CLAY__MAX(lineWidth, measuredWidth); measured->containsNewlines = true; lineWidth = 0; } start = end + 1; } end++; } if (end - start > 0) { Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = end - start, .chars = &text->chars[start], .baseChars = text->chars }, config, context->measureTextUserData); Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = end - start, .width = dimensions.width, .next = -1 }, previousWord); lineWidth += dimensions.width; measuredHeight = CLAY__MAX(measuredHeight, dimensions.height); measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth); } measuredWidth = CLAY__MAX(lineWidth, measuredWidth) - config->letterSpacing; measured->measuredWordsStartIndex = tempWord.next; measured->unwrappedDimensions.width = measuredWidth; measured->unwrappedDimensions.height = measuredHeight; if (elementIndexPrevious != 0) { Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, elementIndexPrevious)->nextIndex = newItemIndex; } else { context->measureTextHashMap.internalArray[hashBucket] = newItemIndex; } return measured; } bool Clay__PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height; } Clay_LayoutElementHashMapItem* Clay__AddHashMapItem(Clay_ElementId elementId, Clay_LayoutElement* layoutElement) { Clay_Context* context = Clay_GetCurrentContext(); if (context->layoutElementsHashMapInternal.length == context->layoutElementsHashMapInternal.capacity - 1) { return NULL; } Clay_LayoutElementHashMapItem item = { .elementId = elementId, .layoutElement = layoutElement, .nextIndex = -1, .generation = context->generation + 1 }; uint32_t hashBucket = elementId.id % context->layoutElementsHashMap.capacity; int32_t hashItemPrevious = -1; int32_t hashItemIndex = context->layoutElementsHashMap.internalArray[hashBucket]; while (hashItemIndex != -1) { // Just replace collision, not a big deal - leave it up to the end user Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Get(&context->layoutElementsHashMapInternal, hashItemIndex); if (hashItem->elementId.id == elementId.id) { // Collision - resolve based on generation item.nextIndex = hashItem->nextIndex; if (hashItem->generation <= context->generation) { // First collision - assume this is the "same" element hashItem->elementId = elementId; // Make sure to copy this across. If the stringId reference has changed, we should update the hash item to use the new one. hashItem->generation = context->generation + 1; hashItem->layoutElement = layoutElement; hashItem->debugData->collision = false; hashItem->onHoverFunction = NULL; hashItem->hoverFunctionUserData = 0; } else { // Multiple collisions this frame - two elements have the same ID context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_DUPLICATE_ID, .errorText = CLAY_STRING("An element with this ID was already previously declared during this layout."), .userData = context->errorHandler.userData }); if (context->debugModeEnabled) { hashItem->debugData->collision = true; } } return hashItem; } hashItemPrevious = hashItemIndex; hashItemIndex = hashItem->nextIndex; } Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Add(&context->layoutElementsHashMapInternal, item); hashItem->debugData = Clay__DebugElementDataArray_Add(&context->debugElementData, CLAY__INIT(Clay__DebugElementData) CLAY__DEFAULT_STRUCT); if (hashItemPrevious != -1) { Clay__LayoutElementHashMapItemArray_Get(&context->layoutElementsHashMapInternal, hashItemPrevious)->nextIndex = (int32_t)context->layoutElementsHashMapInternal.length - 1; } else { context->layoutElementsHashMap.internalArray[hashBucket] = (int32_t)context->layoutElementsHashMapInternal.length - 1; } return hashItem; } Clay_LayoutElementHashMapItem *Clay__GetHashMapItem(uint32_t id) { Clay_Context* context = Clay_GetCurrentContext(); uint32_t hashBucket = id % context->layoutElementsHashMap.capacity; int32_t elementIndex = context->layoutElementsHashMap.internalArray[hashBucket]; while (elementIndex != -1) { Clay_LayoutElementHashMapItem *hashEntry = Clay__LayoutElementHashMapItemArray_Get(&context->layoutElementsHashMapInternal, elementIndex); if (hashEntry->elementId.id == id) { return hashEntry; } elementIndex = hashEntry->nextIndex; } return &Clay_LayoutElementHashMapItem_DEFAULT; } Clay_ElementId Clay__GenerateIdForAnonymousElement(Clay_LayoutElement *openLayoutElement) { Clay_Context* context = Clay_GetCurrentContext(); Clay_LayoutElement *parentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); uint32_t offset = parentElement->childrenOrTextContent.children.length + parentElement->floatingChildrenCount; Clay_ElementId elementId = Clay__HashNumber(offset, parentElement->id); openLayoutElement->id = elementId.id; Clay__AddHashMapItem(elementId, openLayoutElement); Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); return elementId; } bool Clay__ElementHasConfig(Clay_LayoutElement *layoutElement, Clay__ElementConfigType type) { for (int32_t i = 0; i < layoutElement->elementConfigs.length; i++) { if (Clay__ElementConfigArraySlice_Get(&layoutElement->elementConfigs, i)->type == type) { return true; } } return false; } void Clay__UpdateAspectRatioBox(Clay_LayoutElement *layoutElement) { for (int32_t j = 0; j < layoutElement->elementConfigs.length; j++) { Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&layoutElement->elementConfigs, j); if (config->type == CLAY__ELEMENT_CONFIG_TYPE_ASPECT) { Clay_AspectRatioElementConfig *aspectConfig = config->config.aspectRatioElementConfig; if (aspectConfig->aspectRatio == 0) { break; } if (layoutElement->dimensions.width == 0 && layoutElement->dimensions.height != 0) { layoutElement->dimensions.width = layoutElement->dimensions.height * aspectConfig->aspectRatio; } else if (layoutElement->dimensions.width != 0 && layoutElement->dimensions.height == 0) { layoutElement->dimensions.height = layoutElement->dimensions.width * (1 / aspectConfig->aspectRatio); } break; } } } void Clay__CloseElement(void) { Clay_Context* context = Clay_GetCurrentContext(); if (context->booleanWarnings.maxElementsExceeded) { return; } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); Clay_LayoutConfig *layoutConfig = openLayoutElement->layoutConfig; if (!layoutConfig) { openLayoutElement->layoutConfig = &Clay_LayoutConfig_DEFAULT; layoutConfig = &Clay_LayoutConfig_DEFAULT; } bool elementHasClipHorizontal = false; bool elementHasClipVertical = false; for (int32_t i = 0; i < openLayoutElement->elementConfigs.length; i++) { Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&openLayoutElement->elementConfigs, i); if (config->type == CLAY__ELEMENT_CONFIG_TYPE_CLIP) { elementHasClipHorizontal = config->config.clipElementConfig->horizontal; elementHasClipVertical = config->config.clipElementConfig->vertical; context->openClipElementStack.length--; break; } else if (config->type == CLAY__ELEMENT_CONFIG_TYPE_FLOATING) { context->openClipElementStack.length--; } } float leftRightPadding = (float)(layoutConfig->padding.left + layoutConfig->padding.right); float topBottomPadding = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom); // Attach children to the current open element openLayoutElement->childrenOrTextContent.children.elements = &context->layoutElementChildren.internalArray[context->layoutElementChildren.length]; if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { openLayoutElement->dimensions.width = leftRightPadding; openLayoutElement->minDimensions.width = leftRightPadding; for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) { int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i); Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex); openLayoutElement->dimensions.width += child->dimensions.width; openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + topBottomPadding); // Minimum size of child elements doesn't matter to clip containers as they can shrink and hide their contents if (!elementHasClipHorizontal) { openLayoutElement->minDimensions.width += child->minDimensions.width; } if (!elementHasClipVertical) { openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + topBottomPadding); } Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); } float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); openLayoutElement->dimensions.width += childGap; if (!elementHasClipHorizontal) { openLayoutElement->minDimensions.width += childGap; } } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { openLayoutElement->dimensions.height = topBottomPadding; openLayoutElement->minDimensions.height = topBottomPadding; for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) { int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i); Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex); openLayoutElement->dimensions.height += child->dimensions.height; openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + leftRightPadding); // Minimum size of child elements doesn't matter to clip containers as they can shrink and hide their contents if (!elementHasClipVertical) { openLayoutElement->minDimensions.height += child->minDimensions.height; } if (!elementHasClipHorizontal) { openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + leftRightPadding); } Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); } float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); openLayoutElement->dimensions.height += childGap; if (!elementHasClipVertical) { openLayoutElement->minDimensions.height += childGap; } } context->layoutElementChildrenBuffer.length -= openLayoutElement->childrenOrTextContent.children.length; // Clamp element min and max width to the values configured in the layout if (layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { if (layoutConfig->sizing.width.size.minMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier layoutConfig->sizing.width.size.minMax.max = CLAY__MAXFLOAT; } openLayoutElement->dimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); openLayoutElement->minDimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); } else { openLayoutElement->dimensions.width = 0; } // Clamp element min and max height to the values configured in the layout if (layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { if (layoutConfig->sizing.height.size.minMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier layoutConfig->sizing.height.size.minMax.max = CLAY__MAXFLOAT; } openLayoutElement->dimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); openLayoutElement->minDimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); } else { openLayoutElement->dimensions.height = 0; } Clay__UpdateAspectRatioBox(openLayoutElement); bool elementIsFloating = Clay__ElementHasConfig(openLayoutElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING); // Close the currently open element int32_t closingElementIndex = Clay__int32_tArray_RemoveSwapback(&context->openLayoutElementStack, (int)context->openLayoutElementStack.length - 1); // Get the currently open parent openLayoutElement = Clay__GetOpenLayoutElement(); if (context->openLayoutElementStack.length > 1) { if(elementIsFloating) { openLayoutElement->floatingChildrenCount++; return; } openLayoutElement->childrenOrTextContent.children.length++; Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, closingElementIndex); } } bool Clay__MemCmp(const char *s1, const char *s2, int32_t length); #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) bool Clay__MemCmp(const char *s1, const char *s2, int32_t length) { while (length >= 16) { __m128i v1 = _mm_loadu_si128((const __m128i *)s1); __m128i v2 = _mm_loadu_si128((const __m128i *)s2); if (_mm_movemask_epi8(_mm_cmpeq_epi8(v1, v2)) != 0xFFFF) { // If any byte differs return false; } s1 += 16; s2 += 16; length -= 16; } // Handle remaining bytes while (length--) { if (*s1 != *s2) { return false; } s1++; s2++; } return true; } #elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) bool Clay__MemCmp(const char *s1, const char *s2, int32_t length) { while (length >= 16) { uint8x16_t v1 = vld1q_u8((const uint8_t *)s1); uint8x16_t v2 = vld1q_u8((const uint8_t *)s2); // Compare vectors if (vminvq_u32(vreinterpretq_u32_u8(vceqq_u8(v1, v2))) != 0xFFFFFFFF) { // If there's a difference return false; } s1 += 16; s2 += 16; length -= 16; } // Handle remaining bytes while (length--) { if (*s1 != *s2) { return false; } s1++; s2++; } return true; } #else bool Clay__MemCmp(const char *s1, const char *s2, int32_t length) { for (int32_t i = 0; i < length; i++) { if (s1[i] != s2[i]) { return false; } } return true; } #endif void Clay__OpenElement(void) { Clay_Context* context = Clay_GetCurrentContext(); if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { context->booleanWarnings.maxElementsExceeded = true; return; } Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; Clay_LayoutElement* openLayoutElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); Clay__int32_tArray_Add(&context->openLayoutElementStack, context->layoutElements.length - 1); Clay__GenerateIdForAnonymousElement(openLayoutElement); if (context->openClipElementStack.length > 0) { Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); } else { Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, 0); } } void Clay__OpenElementWithId(Clay_ElementId elementId) { Clay_Context* context = Clay_GetCurrentContext(); if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { context->booleanWarnings.maxElementsExceeded = true; return; } Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; layoutElement.id = elementId.id; Clay_LayoutElement * openLayoutElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); Clay__int32_tArray_Add(&context->openLayoutElementStack, context->layoutElements.length - 1); Clay__AddHashMapItem(elementId, openLayoutElement); Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); if (context->openClipElementStack.length > 0) { Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); } else { Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, 0); } } void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig) { Clay_Context* context = Clay_GetCurrentContext(); if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { context->booleanWarnings.maxElementsExceeded = true; return; } Clay_LayoutElement *parentElement = Clay__GetOpenLayoutElement(); Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; Clay_LayoutElement *textElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); if (context->openClipElementStack.length > 0) { Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); } else { Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, 0); } Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, context->layoutElements.length - 1); Clay__MeasureTextCacheItem *textMeasured = Clay__MeasureTextCached(&text, textConfig); Clay_ElementId elementId = Clay__HashNumber(parentElement->childrenOrTextContent.children.length + parentElement->floatingChildrenCount, parentElement->id); textElement->id = elementId.id; Clay__AddHashMapItem(elementId, textElement); Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); Clay_Dimensions textDimensions = { .width = textMeasured->unwrappedDimensions.width, .height = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textMeasured->unwrappedDimensions.height }; textElement->dimensions = textDimensions; textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->minWidth, .height = textDimensions.height }; textElement->childrenOrTextContent.textElementData = Clay__TextElementDataArray_Add(&context->textElementData, CLAY__INIT(Clay__TextElementData) { .text = text, .preferredDimensions = textMeasured->unwrappedDimensions, .elementIndex = context->layoutElements.length - 1 }); textElement->elementConfigs = CLAY__INIT(Clay__ElementConfigArraySlice) { .length = 1, .internalArray = Clay__ElementConfigArray_Add(&context->elementConfigs, CLAY__INIT(Clay_ElementConfig) { .type = CLAY__ELEMENT_CONFIG_TYPE_TEXT, .config = { .textElementConfig = textConfig }}) }; textElement->layoutConfig = &CLAY_LAYOUT_DEFAULT; parentElement->childrenOrTextContent.children.length++; } void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) { Clay_Context* context = Clay_GetCurrentContext(); Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); openLayoutElement->layoutConfig = Clay__StoreLayoutConfig(declaration->layout); if ((declaration->layout.sizing.width.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.width.size.percent > 1) || (declaration->layout.sizing.height.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.height.size.percent > 1)) { context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_PERCENTAGE_OVER_1, .errorText = CLAY_STRING("An element was configured with CLAY_SIZING_PERCENT, but the provided percentage value was over 1.0. Clay expects a value between 0 and 1, i.e. 20% is 0.2."), .userData = context->errorHandler.userData }); } openLayoutElement->elementConfigs.internalArray = &context->elementConfigs.internalArray[context->elementConfigs.length]; Clay_SharedElementConfig *sharedConfig = NULL; if (declaration->backgroundColor.a > 0) { sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .backgroundColor = declaration->backgroundColor }); Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED); } if (!Clay__MemCmp((char *)(&declaration->cornerRadius), (char *)(&Clay__CornerRadius_DEFAULT), sizeof(Clay_CornerRadius))) { if (sharedConfig) { sharedConfig->cornerRadius = declaration->cornerRadius; } else { sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .cornerRadius = declaration->cornerRadius }); Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED); } } if (declaration->userData != 0) { if (sharedConfig) { sharedConfig->userData = declaration->userData; } else { sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .userData = declaration->userData }); Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED); } } if (declaration->image.imageData) { Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = Clay__StoreImageElementConfig(declaration->image) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE); } if (declaration->aspectRatio.aspectRatio > 0) { Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .aspectRatioElementConfig = Clay__StoreAspectRatioElementConfig(declaration->aspectRatio) }, CLAY__ELEMENT_CONFIG_TYPE_ASPECT); Clay__int32_tArray_Add(&context->aspectRatioElementIndexes, context->layoutElements.length - 1); } if (declaration->floating.attachTo != CLAY_ATTACH_TO_NONE) { Clay_FloatingElementConfig floatingConfig = declaration->floating; // This looks dodgy but because of the auto generated root element the depth of the tree will always be at least 2 here Clay_LayoutElement *hierarchicalParent = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); if (hierarchicalParent) { uint32_t clipElementId = 0; if (declaration->floating.attachTo == CLAY_ATTACH_TO_PARENT) { // Attach to the element's direct hierarchical parent floatingConfig.parentId = hierarchicalParent->id; if (context->openClipElementStack.length > 0) { clipElementId = Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1); } } else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) { Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingConfig.parentId); if (parentItem == &Clay_LayoutElementHashMapItem_DEFAULT) { context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, .errorText = CLAY_STRING("A floating element was declared with a parentId, but no element with that ID was found."), .userData = context->errorHandler.userData }); } else { clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(parentItem->layoutElement - context->layoutElements.internalArray)); } } else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ROOT) { floatingConfig.parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0).id; } if (declaration->floating.clipTo == CLAY_CLIP_TO_NONE) { clipElementId = 0; } int32_t currentElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1); Clay__int32_tArray_Set(&context->layoutElementClipElementIds, currentElementIndex, clipElementId); Clay__int32_tArray_Add(&context->openClipElementStack, clipElementId); Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { .layoutElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1), .parentId = floatingConfig.parentId, .clipElementId = clipElementId, .zIndex = floatingConfig.zIndex, }); Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .floatingElementConfig = Clay__StoreFloatingElementConfig(floatingConfig) }, CLAY__ELEMENT_CONFIG_TYPE_FLOATING); } } if (declaration->custom.customData) { Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration->custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM); } if (declaration->clip.horizontal | declaration->clip.vertical) { Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .clipElementConfig = Clay__StoreClipElementConfig(declaration->clip) }, CLAY__ELEMENT_CONFIG_TYPE_CLIP); Clay__int32_tArray_Add(&context->openClipElementStack, (int)openLayoutElement->id); // Retrieve or create cached data to track scroll position across frames Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL; for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); if (openLayoutElement->id == mapping->elementId) { scrollOffset = mapping; scrollOffset->layoutElement = openLayoutElement; scrollOffset->openThisFrame = true; } } if (!scrollOffset) { scrollOffset = Clay__ScrollContainerDataInternalArray_Add(&context->scrollContainerDatas, CLAY__INIT(Clay__ScrollContainerDataInternal){.layoutElement = openLayoutElement, .scrollOrigin = {-1,-1}, .elementId = openLayoutElement->id, .openThisFrame = true}); } if (context->externalScrollHandlingEnabled) { scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId, context->queryScrollOffsetUserData); } } if (!Clay__MemCmp((char *)(&declaration->border.width), (char *)(&Clay__BorderWidth_DEFAULT), sizeof(Clay_BorderWidth))) { Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .borderElementConfig = Clay__StoreBorderElementConfig(declaration->border) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER); } } void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) { Clay__ConfigureOpenElementPtr(&declaration); } void Clay__InitializeEphemeralMemory(Clay_Context* context) { int32_t maxElementCount = context->maxElementCount; // Ephemeral Memory - reset every frame Clay_Arena *arena = &context->internalArena; arena->nextAllocation = context->arenaResetOffset; context->layoutElementChildrenBuffer = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->layoutElements = Clay_LayoutElementArray_Allocate_Arena(maxElementCount, arena); context->warnings = Clay__WarningArray_Allocate_Arena(100, arena); context->layoutConfigs = Clay__LayoutConfigArray_Allocate_Arena(maxElementCount, arena); context->elementConfigs = Clay__ElementConfigArray_Allocate_Arena(maxElementCount, arena); context->textElementConfigs = Clay__TextElementConfigArray_Allocate_Arena(maxElementCount, arena); context->aspectRatioElementConfigs = Clay__AspectRatioElementConfigArray_Allocate_Arena(maxElementCount, arena); context->imageElementConfigs = Clay__ImageElementConfigArray_Allocate_Arena(maxElementCount, arena); context->floatingElementConfigs = Clay__FloatingElementConfigArray_Allocate_Arena(maxElementCount, arena); context->clipElementConfigs = Clay__ClipElementConfigArray_Allocate_Arena(maxElementCount, arena); context->customElementConfigs = Clay__CustomElementConfigArray_Allocate_Arena(maxElementCount, arena); context->borderElementConfigs = Clay__BorderElementConfigArray_Allocate_Arena(maxElementCount, arena); context->sharedElementConfigs = Clay__SharedElementConfigArray_Allocate_Arena(maxElementCount, arena); context->layoutElementIdStrings = Clay__StringArray_Allocate_Arena(maxElementCount, arena); context->wrappedTextLines = Clay__WrappedTextLineArray_Allocate_Arena(maxElementCount, arena); context->layoutElementTreeNodeArray1 = Clay__LayoutElementTreeNodeArray_Allocate_Arena(maxElementCount, arena); context->layoutElementTreeRoots = Clay__LayoutElementTreeRootArray_Allocate_Arena(maxElementCount, arena); context->layoutElementChildren = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->openLayoutElementStack = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->textElementData = Clay__TextElementDataArray_Allocate_Arena(maxElementCount, arena); context->aspectRatioElementIndexes = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->renderCommands = Clay_RenderCommandArray_Allocate_Arena(maxElementCount, arena); context->treeNodeVisited = Clay__boolArray_Allocate_Arena(maxElementCount, arena); context->treeNodeVisited.length = context->treeNodeVisited.capacity; // This array is accessed directly rather than behaving as a list context->openClipElementStack = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->reusableElementIndexBuffer = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->layoutElementClipElementIds = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->dynamicStringData = Clay__charArray_Allocate_Arena(maxElementCount, arena); } void Clay__InitializePersistentMemory(Clay_Context* context) { // Persistent memory - initialized once and not reset int32_t maxElementCount = context->maxElementCount; int32_t maxMeasureTextCacheWordCount = context->maxMeasureTextCacheWordCount; Clay_Arena *arena = &context->internalArena; context->scrollContainerDatas = Clay__ScrollContainerDataInternalArray_Allocate_Arena(100, arena); context->layoutElementsHashMapInternal = Clay__LayoutElementHashMapItemArray_Allocate_Arena(maxElementCount, arena); context->layoutElementsHashMap = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->measureTextHashMapInternal = Clay__MeasureTextCacheItemArray_Allocate_Arena(maxElementCount, arena); context->measureTextHashMapInternalFreeList = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->measuredWordsFreeList = Clay__int32_tArray_Allocate_Arena(maxMeasureTextCacheWordCount, arena); context->measureTextHashMap = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->measuredWords = Clay__MeasuredWordArray_Allocate_Arena(maxMeasureTextCacheWordCount, arena); context->pointerOverIds = Clay_ElementIdArray_Allocate_Arena(maxElementCount, arena); context->debugElementData = Clay__DebugElementDataArray_Allocate_Arena(maxElementCount, arena); context->arenaResetOffset = arena->nextAllocation; } const float CLAY__EPSILON = 0.01; bool Clay__FloatEqual(float left, float right) { float subtracted = left - right; return subtracted < CLAY__EPSILON && subtracted > -CLAY__EPSILON; } void Clay__SizeContainersAlongAxis(bool xAxis) { Clay_Context* context = Clay_GetCurrentContext(); Clay__int32_tArray bfsBuffer = context->layoutElementChildrenBuffer; Clay__int32_tArray resizableContainerBuffer = context->openLayoutElementStack; for (int32_t rootIndex = 0; rootIndex < context->layoutElementTreeRoots.length; ++rootIndex) { bfsBuffer.length = 0; Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)root->layoutElementIndex); Clay__int32_tArray_Add(&bfsBuffer, (int32_t)root->layoutElementIndex); // Size floating containers to their parents if (Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING)) { Clay_FloatingElementConfig *floatingElementConfig = Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig; Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingElementConfig->parentId); if (parentItem && parentItem != &Clay_LayoutElementHashMapItem_DEFAULT) { Clay_LayoutElement *parentLayoutElement = parentItem->layoutElement; switch (rootElement->layoutConfig->sizing.width.type) { case CLAY__SIZING_TYPE_GROW: { rootElement->dimensions.width = parentLayoutElement->dimensions.width; break; } case CLAY__SIZING_TYPE_PERCENT: { rootElement->dimensions.width = parentLayoutElement->dimensions.width * rootElement->layoutConfig->sizing.width.size.percent; break; } default: break; } switch (rootElement->layoutConfig->sizing.height.type) { case CLAY__SIZING_TYPE_GROW: { rootElement->dimensions.height = parentLayoutElement->dimensions.height; break; } case CLAY__SIZING_TYPE_PERCENT: { rootElement->dimensions.height = parentLayoutElement->dimensions.height * rootElement->layoutConfig->sizing.height.size.percent; break; } default: break; } } } if (rootElement->layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->layoutConfig->sizing.width.size.minMax.min), rootElement->layoutConfig->sizing.width.size.minMax.max); } if (rootElement->layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->layoutConfig->sizing.height.size.minMax.min), rootElement->layoutConfig->sizing.height.size.minMax.max); } for (int32_t i = 0; i < bfsBuffer.length; ++i) { int32_t parentIndex = Clay__int32_tArray_GetValue(&bfsBuffer, i); Clay_LayoutElement *parent = Clay_LayoutElementArray_Get(&context->layoutElements, parentIndex); Clay_LayoutConfig *parentStyleConfig = parent->layoutConfig; int32_t growContainerCount = 0; float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height; float parentPadding = (float)(xAxis ? (parent->layoutConfig->padding.left + parent->layoutConfig->padding.right) : (parent->layoutConfig->padding.top + parent->layoutConfig->padding.bottom)); float innerContentSize = 0, totalPaddingAndChildGaps = parentPadding; bool sizingAlongAxis = (xAxis && parentStyleConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentStyleConfig->layoutDirection == CLAY_TOP_TO_BOTTOM); resizableContainerBuffer.length = 0; float parentChildGap = parentStyleConfig->childGap; for (int32_t childOffset = 0; childOffset < parent->childrenOrTextContent.children.length; childOffset++) { int32_t childElementIndex = parent->childrenOrTextContent.children.elements[childOffset]; Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childElementIndex); Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height; if (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && childElement->childrenOrTextContent.children.length > 0) { Clay__int32_tArray_Add(&bfsBuffer, childElementIndex); } if (childSizing.type != CLAY__SIZING_TYPE_PERCENT && childSizing.type != CLAY__SIZING_TYPE_FIXED && (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (Clay__FindElementConfigWithType(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig->wrapMode == CLAY_TEXT_WRAP_WORDS)) // todo too many loops // && (xAxis || !Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT)) ) { Clay__int32_tArray_Add(&resizableContainerBuffer, childElementIndex); } if (sizingAlongAxis) { innerContentSize += (childSizing.type == CLAY__SIZING_TYPE_PERCENT ? 0 : childSize); if (childSizing.type == CLAY__SIZING_TYPE_GROW) { growContainerCount++; } if (childOffset > 0) { innerContentSize += parentChildGap; // For children after index 0, the childAxisOffset is the gap from the previous child totalPaddingAndChildGaps += parentChildGap; } } else { innerContentSize = CLAY__MAX(childSize, innerContentSize); } } // Expand percentage containers to size for (int32_t childOffset = 0; childOffset < parent->childrenOrTextContent.children.length; childOffset++) { int32_t childElementIndex = parent->childrenOrTextContent.children.elements[childOffset]; Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childElementIndex); Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; if (childSizing.type == CLAY__SIZING_TYPE_PERCENT) { *childSize = (parentSize - totalPaddingAndChildGaps) * childSizing.size.percent; if (sizingAlongAxis) { innerContentSize += *childSize; } Clay__UpdateAspectRatioBox(childElement); } } if (sizingAlongAxis) { float sizeToDistribute = parentSize - parentPadding - innerContentSize; // The content is too large, compress the children as much as possible if (sizeToDistribute < 0) { // If the parent clips content in this axis direction, don't compress children, just leave them alone Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; if (clipElementConfig) { if (((xAxis && clipElementConfig->horizontal) || (!xAxis && clipElementConfig->vertical))) { continue; } } // Scrolling containers preferentially compress before others while (sizeToDistribute < -CLAY__EPSILON && resizableContainerBuffer.length > 0) { float largest = 0; float secondLargest = 0; float widthToAdd = sizeToDistribute; for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); float childSize = xAxis ? child->dimensions.width : child->dimensions.height; if (Clay__FloatEqual(childSize, largest)) { continue; } if (childSize > largest) { secondLargest = largest; largest = childSize; } if (childSize < largest) { secondLargest = CLAY__MAX(secondLargest, childSize); widthToAdd = secondLargest - largest; } } widthToAdd = CLAY__MAX(widthToAdd, sizeToDistribute / resizableContainerBuffer.length); for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height; float minSize = xAxis ? child->minDimensions.width : child->minDimensions.height; float previousWidth = *childSize; if (Clay__FloatEqual(*childSize, largest)) { *childSize += widthToAdd; if (*childSize <= minSize) { *childSize = minSize; Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); } sizeToDistribute -= (*childSize - previousWidth); } } } // The content is too small, allow SIZING_GROW containers to expand } else if (sizeToDistribute > 0 && growContainerCount > 0) { for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); Clay__SizingType childSizing = xAxis ? child->layoutConfig->sizing.width.type : child->layoutConfig->sizing.height.type; if (childSizing != CLAY__SIZING_TYPE_GROW) { Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); } } while (sizeToDistribute > CLAY__EPSILON && resizableContainerBuffer.length > 0) { float smallest = CLAY__MAXFLOAT; float secondSmallest = CLAY__MAXFLOAT; float widthToAdd = sizeToDistribute; for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); float childSize = xAxis ? child->dimensions.width : child->dimensions.height; if (Clay__FloatEqual(childSize, smallest)) { continue; } if (childSize < smallest) { secondSmallest = smallest; smallest = childSize; } if (childSize > smallest) { secondSmallest = CLAY__MIN(secondSmallest, childSize); widthToAdd = secondSmallest - smallest; } } widthToAdd = CLAY__MIN(widthToAdd, sizeToDistribute / resizableContainerBuffer.length); for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height; float maxSize = xAxis ? child->layoutConfig->sizing.width.size.minMax.max : child->layoutConfig->sizing.height.size.minMax.max; float previousWidth = *childSize; if (Clay__FloatEqual(*childSize, smallest)) { *childSize += widthToAdd; if (*childSize >= maxSize) { *childSize = maxSize; Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); } sizeToDistribute -= (*childSize - previousWidth); } } } } // Sizing along the non layout axis ("off axis") } else { for (int32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childOffset)); Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; float minSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height; float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; float maxSize = parentSize - parentPadding; // If we're laying out the children of a scroll panel, grow containers expand to the size of the inner content, not the outer container if (Clay__ElementHasConfig(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP)) { Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; if (((xAxis && clipElementConfig->horizontal) || (!xAxis && clipElementConfig->vertical))) { maxSize = CLAY__MAX(maxSize, innerContentSize); } } if (childSizing.type == CLAY__SIZING_TYPE_GROW) { *childSize = CLAY__MIN(maxSize, childSizing.size.minMax.max); } *childSize = CLAY__MAX(minSize, CLAY__MIN(*childSize, maxSize)); } } } } } Clay_String Clay__IntToString(int32_t integer) { if (integer == 0) { return CLAY__INIT(Clay_String) { .length = 1, .chars = "0" }; } Clay_Context* context = Clay_GetCurrentContext(); char *chars = (char *)(context->dynamicStringData.internalArray + context->dynamicStringData.length); int32_t length = 0; int32_t sign = integer; if (integer < 0) { integer = -integer; } while (integer > 0) { chars[length++] = (char)(integer % 10 + '0'); integer /= 10; } if (sign < 0) { chars[length++] = '-'; } // Reverse the string to get the correct order for (int32_t j = 0, k = length - 1; j < k; j++, k--) { char temp = chars[j]; chars[j] = chars[k]; chars[k] = temp; } context->dynamicStringData.length += length; return CLAY__INIT(Clay_String) { .length = length, .chars = chars }; } void Clay__AddRenderCommand(Clay_RenderCommand renderCommand) { Clay_Context* context = Clay_GetCurrentContext(); if (context->renderCommands.length < context->renderCommands.capacity - 1) { Clay_RenderCommandArray_Add(&context->renderCommands, renderCommand); } else { if (!context->booleanWarnings.maxRenderCommandsExceeded) { context->booleanWarnings.maxRenderCommandsExceeded = true; context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, .errorText = CLAY_STRING("Clay ran out of capacity while attempting to create render commands. This is usually caused by a large amount of wrapping text elements while close to the max element capacity. Try using Clay_SetMaxElementCount() with a higher value."), .userData = context->errorHandler.userData }); } } } bool Clay__ElementIsOffscreen(Clay_BoundingBox *boundingBox) { Clay_Context* context = Clay_GetCurrentContext(); if (context->disableCulling) { return false; } return (boundingBox->x > (float)context->layoutDimensions.width) || (boundingBox->y > (float)context->layoutDimensions.height) || (boundingBox->x + boundingBox->width < 0) || (boundingBox->y + boundingBox->height < 0); } void Clay__CalculateFinalLayout(void) { Clay_Context* context = Clay_GetCurrentContext(); // Calculate sizing along the X axis Clay__SizeContainersAlongAxis(true); // Wrap text for (int32_t textElementIndex = 0; textElementIndex < context->textElementData.length; ++textElementIndex) { Clay__TextElementData *textElementData = Clay__TextElementDataArray_Get(&context->textElementData, textElementIndex); textElementData->wrappedLines = CLAY__INIT(Clay__WrappedTextLineArraySlice) { .length = 0, .internalArray = &context->wrappedTextLines.internalArray[context->wrappedTextLines.length] }; Clay_LayoutElement *containerElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)textElementData->elementIndex); Clay_TextElementConfig *textConfig = Clay__FindElementConfigWithType(containerElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig; Clay__MeasureTextCacheItem *measureTextCacheItem = Clay__MeasureTextCached(&textElementData->text, textConfig); float lineWidth = 0; float lineHeight = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textElementData->preferredDimensions.height; int32_t lineLengthChars = 0; int32_t lineStartOffset = 0; if (!measureTextCacheItem->containsNewlines && textElementData->preferredDimensions.width <= containerElement->dimensions.width) { Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { containerElement->dimensions, textElementData->text }); textElementData->wrappedLines.length++; continue; } float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, textConfig, context->measureTextUserData).width; int32_t wordIndex = measureTextCacheItem->measuredWordsStartIndex; while (wordIndex != -1) { if (context->wrappedTextLines.length > context->wrappedTextLines.capacity - 1) { break; } Clay__MeasuredWord *measuredWord = Clay__MeasuredWordArray_Get(&context->measuredWords, wordIndex); // Only word on the line is too large, just render it anyway if (lineLengthChars == 0 && lineWidth + measuredWord->width > containerElement->dimensions.width) { Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { measuredWord->width, lineHeight }, { .length = measuredWord->length, .chars = &textElementData->text.chars[measuredWord->startOffset] } }); textElementData->wrappedLines.length++; wordIndex = measuredWord->next; lineStartOffset = measuredWord->startOffset + measuredWord->length; } // measuredWord->length == 0 means a newline character else if (measuredWord->length == 0 || lineWidth + measuredWord->width > containerElement->dimensions.width) { // Wrapped text lines list has overflowed, just render out the line bool finalCharIsSpace = textElementData->text.chars[CLAY__MAX(lineStartOffset + lineLengthChars - 1, 0)] == ' '; Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth + (finalCharIsSpace ? -spaceWidth : 0), lineHeight }, { .length = lineLengthChars + (finalCharIsSpace ? -1 : 0), .chars = &textElementData->text.chars[lineStartOffset] } }); textElementData->wrappedLines.length++; if (lineLengthChars == 0 || measuredWord->length == 0) { wordIndex = measuredWord->next; } lineWidth = 0; lineLengthChars = 0; lineStartOffset = measuredWord->startOffset; } else { lineWidth += measuredWord->width + textConfig->letterSpacing; lineLengthChars += measuredWord->length; wordIndex = measuredWord->next; } } if (lineLengthChars > 0) { Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth - textConfig->letterSpacing, lineHeight }, {.length = lineLengthChars, .chars = &textElementData->text.chars[lineStartOffset] } }); textElementData->wrappedLines.length++; } containerElement->dimensions.height = lineHeight * (float)textElementData->wrappedLines.length; } // Scale vertical heights according to aspect ratio for (int32_t i = 0; i < context->aspectRatioElementIndexes.length; ++i) { Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->aspectRatioElementIndexes, i)); Clay_AspectRatioElementConfig *config = Clay__FindElementConfigWithType(aspectElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; aspectElement->dimensions.height = (1 / config->aspectRatio) * aspectElement->dimensions.width; aspectElement->layoutConfig->sizing.height.size.minMax.max = aspectElement->dimensions.height; } // Propagate effect of text wrapping, aspect scaling etc. on height of parents Clay__LayoutElementTreeNodeArray dfsBuffer = context->layoutElementTreeNodeArray1; dfsBuffer.length = 0; for (int32_t i = 0; i < context->layoutElementTreeRoots.length; ++i) { Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, i); context->treeNodeVisited.internalArray[dfsBuffer.length] = false; Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)root->layoutElementIndex) }); } while (dfsBuffer.length > 0) { Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; if (!context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; // If the element has no children or is the container for a text element, don't bother inspecting it if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0) { dfsBuffer.length--; continue; } // Add the children to the DFS buffer (needs to be pushed in reverse so that stack traversal is in correct layout order) for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; i++) { context->treeNodeVisited.internalArray[dfsBuffer.length] = false; Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]) }); } continue; } dfsBuffer.length--; // DFS node has been visited, this is on the way back up to the root Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig; if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { // Resize any parent containers that have grown in height along their non layout axis for (int32_t j = 0; j < currentElement->childrenOrTextContent.children.length; ++j) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[j]); float childHeightWithPadding = CLAY__MAX(childElement->dimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom, currentElement->dimensions.height); currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(childHeightWithPadding, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); } } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { // Resizing along the layout axis float contentHeight = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom); for (int32_t j = 0; j < currentElement->childrenOrTextContent.children.length; ++j) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[j]); contentHeight += childElement->dimensions.height; } contentHeight += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(contentHeight, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); } } // Calculate sizing along the Y axis Clay__SizeContainersAlongAxis(false); // Scale horizontal widths according to aspect ratio for (int32_t i = 0; i < context->aspectRatioElementIndexes.length; ++i) { Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->aspectRatioElementIndexes, i)); Clay_AspectRatioElementConfig *config = Clay__FindElementConfigWithType(aspectElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; aspectElement->dimensions.width = config->aspectRatio * aspectElement->dimensions.height; } // Sort tree roots by z-index int32_t sortMax = context->layoutElementTreeRoots.length - 1; while (sortMax > 0) { // todo dumb bubble sort for (int32_t i = 0; i < sortMax; ++i) { Clay__LayoutElementTreeRoot current = *Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, i); Clay__LayoutElementTreeRoot next = *Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, i + 1); if (next.zIndex < current.zIndex) { Clay__LayoutElementTreeRootArray_Set(&context->layoutElementTreeRoots, i, next); Clay__LayoutElementTreeRootArray_Set(&context->layoutElementTreeRoots, i + 1, current); } } sortMax--; } // Calculate final positions and generate render commands context->renderCommands.length = 0; dfsBuffer.length = 0; for (int32_t rootIndex = 0; rootIndex < context->layoutElementTreeRoots.length; ++rootIndex) { dfsBuffer.length = 0; Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)root->layoutElementIndex); Clay_Vector2 rootPosition = CLAY__DEFAULT_STRUCT; Clay_LayoutElementHashMapItem *parentHashMapItem = Clay__GetHashMapItem(root->parentId); // Position root floating containers if (Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING) && parentHashMapItem) { Clay_FloatingElementConfig *config = Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig; Clay_Dimensions rootDimensions = rootElement->dimensions; Clay_BoundingBox parentBoundingBox = parentHashMapItem->boundingBox; // Set X position Clay_Vector2 targetAttachPosition = CLAY__DEFAULT_STRUCT; switch (config->attachPoints.parent) { case CLAY_ATTACH_POINT_LEFT_TOP: case CLAY_ATTACH_POINT_LEFT_CENTER: case CLAY_ATTACH_POINT_LEFT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x; break; case CLAY_ATTACH_POINT_CENTER_TOP: case CLAY_ATTACH_POINT_CENTER_CENTER: case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + (parentBoundingBox.width / 2); break; case CLAY_ATTACH_POINT_RIGHT_TOP: case CLAY_ATTACH_POINT_RIGHT_CENTER: case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + parentBoundingBox.width; break; } switch (config->attachPoints.element) { case CLAY_ATTACH_POINT_LEFT_TOP: case CLAY_ATTACH_POINT_LEFT_CENTER: case CLAY_ATTACH_POINT_LEFT_BOTTOM: break; case CLAY_ATTACH_POINT_CENTER_TOP: case CLAY_ATTACH_POINT_CENTER_CENTER: case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x -= (rootDimensions.width / 2); break; case CLAY_ATTACH_POINT_RIGHT_TOP: case CLAY_ATTACH_POINT_RIGHT_CENTER: case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x -= rootDimensions.width; break; } switch (config->attachPoints.parent) { // I know I could merge the x and y switch statements, but this is easier to read case CLAY_ATTACH_POINT_LEFT_TOP: case CLAY_ATTACH_POINT_RIGHT_TOP: case CLAY_ATTACH_POINT_CENTER_TOP: targetAttachPosition.y = parentBoundingBox.y; break; case CLAY_ATTACH_POINT_LEFT_CENTER: case CLAY_ATTACH_POINT_CENTER_CENTER: case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y = parentBoundingBox.y + (parentBoundingBox.height / 2); break; case CLAY_ATTACH_POINT_LEFT_BOTTOM: case CLAY_ATTACH_POINT_CENTER_BOTTOM: case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y = parentBoundingBox.y + parentBoundingBox.height; break; } switch (config->attachPoints.element) { case CLAY_ATTACH_POINT_LEFT_TOP: case CLAY_ATTACH_POINT_RIGHT_TOP: case CLAY_ATTACH_POINT_CENTER_TOP: break; case CLAY_ATTACH_POINT_LEFT_CENTER: case CLAY_ATTACH_POINT_CENTER_CENTER: case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y -= (rootDimensions.height / 2); break; case CLAY_ATTACH_POINT_LEFT_BOTTOM: case CLAY_ATTACH_POINT_CENTER_BOTTOM: case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y -= rootDimensions.height; break; } targetAttachPosition.x += config->offset.x; targetAttachPosition.y += config->offset.y; rootPosition = targetAttachPosition; } if (root->clipElementId) { Clay_LayoutElementHashMapItem *clipHashMapItem = Clay__GetHashMapItem(root->clipElementId); if (clipHashMapItem) { // Floating elements that are attached to scrolling contents won't be correctly positioned if external scroll handling is enabled, fix here if (context->externalScrollHandlingEnabled) { Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(clipHashMapItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; if (clipConfig->horizontal) { rootPosition.x += clipConfig->childOffset.x; } if (clipConfig->vertical) { rootPosition.y += clipConfig->childOffset.y; } } Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .boundingBox = clipHashMapItem->boundingBox, .userData = 0, .id = Clay__HashNumber(rootElement->id, rootElement->childrenOrTextContent.children.length + 10).id, // TODO need a better strategy for managing derived ids .zIndex = root->zIndex, .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, }); } } Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = rootElement, .position = rootPosition, .nextChildOffset = { .x = (float)rootElement->layoutConfig->padding.left, .y = (float)rootElement->layoutConfig->padding.top } }); context->treeNodeVisited.internalArray[0] = false; while (dfsBuffer.length > 0) { Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig; Clay_Vector2 scrollOffset = CLAY__DEFAULT_STRUCT; // This will only be run a single time for each element in downwards DFS order if (!context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; Clay_BoundingBox currentElementBoundingBox = { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height }; if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING)) { Clay_FloatingElementConfig *floatingElementConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig; Clay_Dimensions expand = floatingElementConfig->expand; currentElementBoundingBox.x -= expand.width; currentElementBoundingBox.width += expand.width * 2; currentElementBoundingBox.y -= expand.height; currentElementBoundingBox.height += expand.height * 2; } Clay__ScrollContainerDataInternal *scrollContainerData = CLAY__NULL; // Apply scroll offsets to container if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP)) { Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; // This linear scan could theoretically be slow under very strange conditions, but I can't imagine a real UI with more than a few 10's of scroll containers for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); if (mapping->layoutElement == currentElement) { scrollContainerData = mapping; mapping->boundingBox = currentElementBoundingBox; scrollOffset = clipConfig->childOffset; if (context->externalScrollHandlingEnabled) { scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; } break; } } } Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(currentElement->id); if (hashMapItem) { hashMapItem->boundingBox = currentElementBoundingBox; } int32_t sortedConfigIndexes[20]; for (int32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { sortedConfigIndexes[elementConfigIndex] = elementConfigIndex; } sortMax = currentElement->elementConfigs.length - 1; while (sortMax > 0) { // todo dumb bubble sort for (int32_t i = 0; i < sortMax; ++i) { int32_t current = sortedConfigIndexes[i]; int32_t next = sortedConfigIndexes[i + 1]; Clay__ElementConfigType currentType = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, current)->type; Clay__ElementConfigType nextType = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, next)->type; if (nextType == CLAY__ELEMENT_CONFIG_TYPE_CLIP || currentType == CLAY__ELEMENT_CONFIG_TYPE_BORDER) { sortedConfigIndexes[i] = next; sortedConfigIndexes[i + 1] = current; } } sortMax--; } bool emitRectangle = false; // Create the render commands for this element Clay_SharedElementConfig *sharedConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SHARED).sharedElementConfig; if (sharedConfig && sharedConfig->backgroundColor.a > 0) { emitRectangle = true; } else if (!sharedConfig) { emitRectangle = false; sharedConfig = &Clay_SharedElementConfig_DEFAULT; } for (int32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, sortedConfigIndexes[elementConfigIndex]); Clay_RenderCommand renderCommand = { .boundingBox = currentElementBoundingBox, .userData = sharedConfig->userData, .id = currentElement->id, }; bool offscreen = Clay__ElementIsOffscreen(¤tElementBoundingBox); // Culling - Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow bool shouldRender = !offscreen; switch (elementConfig->type) { case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: case CLAY__ELEMENT_CONFIG_TYPE_SHARED: case CLAY__ELEMENT_CONFIG_TYPE_BORDER: { shouldRender = false; break; } case CLAY__ELEMENT_CONFIG_TYPE_CLIP: { renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START; renderCommand.renderData = CLAY__INIT(Clay_RenderData) { .clip = { .horizontal = elementConfig->config.clipElementConfig->horizontal, .vertical = elementConfig->config.clipElementConfig->vertical, } }; break; } case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE; renderCommand.renderData = CLAY__INIT(Clay_RenderData) { .image = { .backgroundColor = sharedConfig->backgroundColor, .cornerRadius = sharedConfig->cornerRadius, .imageData = elementConfig->config.imageElementConfig->imageData, } }; emitRectangle = false; break; } case CLAY__ELEMENT_CONFIG_TYPE_TEXT: { if (!shouldRender) { break; } shouldRender = false; Clay_ElementConfigUnion configUnion = elementConfig->config; Clay_TextElementConfig *textElementConfig = configUnion.textElementConfig; float naturalLineHeight = currentElement->childrenOrTextContent.textElementData->preferredDimensions.height; float finalLineHeight = textElementConfig->lineHeight > 0 ? (float)textElementConfig->lineHeight : naturalLineHeight; float lineHeightOffset = (finalLineHeight - naturalLineHeight) / 2; float yPosition = lineHeightOffset; for (int32_t lineIndex = 0; lineIndex < currentElement->childrenOrTextContent.textElementData->wrappedLines.length; ++lineIndex) { Clay__WrappedTextLine *wrappedLine = Clay__WrappedTextLineArraySlice_Get(¤tElement->childrenOrTextContent.textElementData->wrappedLines, lineIndex); if (wrappedLine->line.length == 0) { yPosition += finalLineHeight; continue; } float offset = (currentElementBoundingBox.width - wrappedLine->dimensions.width); if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_LEFT) { offset = 0; } if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { offset /= 2; } Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .boundingBox = { currentElementBoundingBox.x + offset, currentElementBoundingBox.y + yPosition, wrappedLine->dimensions.width, wrappedLine->dimensions.height }, .renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = wrappedLine->line.length, .chars = wrappedLine->line.chars, .baseChars = currentElement->childrenOrTextContent.textElementData->text.chars }, .textColor = textElementConfig->textColor, .fontId = textElementConfig->fontId, .fontSize = textElementConfig->fontSize, .letterSpacing = textElementConfig->letterSpacing, .lineHeight = textElementConfig->lineHeight, }}, .userData = textElementConfig->userData, .id = Clay__HashNumber(lineIndex, currentElement->id).id, .zIndex = root->zIndex, .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT, }); yPosition += finalLineHeight; if (!context->disableCulling && (currentElementBoundingBox.y + yPosition > context->layoutDimensions.height)) { break; } } break; } case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: { renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_CUSTOM; renderCommand.renderData = CLAY__INIT(Clay_RenderData) { .custom = { .backgroundColor = sharedConfig->backgroundColor, .cornerRadius = sharedConfig->cornerRadius, .customData = elementConfig->config.customElementConfig->customData, } }; emitRectangle = false; break; } default: break; } if (shouldRender) { Clay__AddRenderCommand(renderCommand); } if (offscreen) { // NOTE: You may be tempted to try an early return / continue if an element is off screen. Why bother calculating layout for its children, right? // Unfortunately, a FLOATING_CONTAINER may be defined that attaches to a child or grandchild of this element, which is large enough to still // be on screen, even if this element isn't. That depends on this element and it's children being laid out correctly (even if they are entirely off screen) } } if (emitRectangle) { Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .boundingBox = currentElementBoundingBox, .renderData = { .rectangle = { .backgroundColor = sharedConfig->backgroundColor, .cornerRadius = sharedConfig->cornerRadius, }}, .userData = sharedConfig->userData, .id = currentElement->id, .zIndex = root->zIndex, .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, }); } // Setup initial on-axis alignment if (!Clay__ElementHasConfig(currentElementTreeNode->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { Clay_Dimensions contentSize = {0,0}; if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); contentSize.width += childElement->dimensions.width; contentSize.height = CLAY__MAX(contentSize.height, childElement->dimensions.height); } contentSize.width += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); float extraSpace = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - contentSize.width; switch (layoutConfig->childAlignment.x) { case CLAY_ALIGN_X_LEFT: extraSpace = 0; break; case CLAY_ALIGN_X_CENTER: extraSpace /= 2; break; default: break; } currentElementTreeNode->nextChildOffset.x += extraSpace; extraSpace = CLAY__MAX(0, extraSpace); } else { for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); contentSize.width = CLAY__MAX(contentSize.width, childElement->dimensions.width); contentSize.height += childElement->dimensions.height; } contentSize.height += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); float extraSpace = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - contentSize.height; switch (layoutConfig->childAlignment.y) { case CLAY_ALIGN_Y_TOP: extraSpace = 0; break; case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break; default: break; } extraSpace = CLAY__MAX(0, extraSpace); currentElementTreeNode->nextChildOffset.y += extraSpace; } if (scrollContainerData) { scrollContainerData->contentSize = CLAY__INIT(Clay_Dimensions) { contentSize.width + (float)(layoutConfig->padding.left + layoutConfig->padding.right), contentSize.height + (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) }; } } } else { // DFS is returning upwards backwards bool closeClipElement = false; Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; if (clipConfig) { closeClipElement = true; for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); if (mapping->layoutElement == currentElement) { scrollOffset = clipConfig->childOffset; if (context->externalScrollHandlingEnabled) { scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; } break; } } } if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_BORDER)) { Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); Clay_BoundingBox currentElementBoundingBox = currentElementData->boundingBox; // Culling - Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow if (!Clay__ElementIsOffscreen(¤tElementBoundingBox)) { Clay_SharedElementConfig *sharedConfig = Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SHARED) ? Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SHARED).sharedElementConfig : &Clay_SharedElementConfig_DEFAULT; Clay_BorderElementConfig *borderConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_BORDER).borderElementConfig; Clay_RenderCommand renderCommand = { .boundingBox = currentElementBoundingBox, .renderData = { .border = { .color = borderConfig->color, .cornerRadius = sharedConfig->cornerRadius, .width = borderConfig->width }}, .userData = sharedConfig->userData, .id = Clay__HashNumber(currentElement->id, currentElement->childrenOrTextContent.children.length).id, .commandType = CLAY_RENDER_COMMAND_TYPE_BORDER, }; Clay__AddRenderCommand(renderCommand); if (borderConfig->width.betweenChildren > 0 && borderConfig->color.a > 0) { float halfGap = layoutConfig->childGap / 2; Clay_Vector2 borderOffset = { (float)layoutConfig->padding.left - halfGap, (float)layoutConfig->padding.top - halfGap }; if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); if (i > 0) { Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .boundingBox = { currentElementBoundingBox.x + borderOffset.x + scrollOffset.x, currentElementBoundingBox.y + scrollOffset.y, (float)borderConfig->width.betweenChildren, currentElement->dimensions.height }, .renderData = { .rectangle = { .backgroundColor = borderConfig->color, } }, .userData = sharedConfig->userData, .id = Clay__HashNumber(currentElement->id, currentElement->childrenOrTextContent.children.length + 1 + i).id, .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, }); } borderOffset.x += (childElement->dimensions.width + (float)layoutConfig->childGap); } } else { for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); if (i > 0) { Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .boundingBox = { currentElementBoundingBox.x + scrollOffset.x, currentElementBoundingBox.y + borderOffset.y + scrollOffset.y, currentElement->dimensions.width, (float)borderConfig->width.betweenChildren }, .renderData = { .rectangle = { .backgroundColor = borderConfig->color, } }, .userData = sharedConfig->userData, .id = Clay__HashNumber(currentElement->id, currentElement->childrenOrTextContent.children.length + 1 + i).id, .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, }); } borderOffset.y += (childElement->dimensions.height + (float)layoutConfig->childGap); } } } } } // This exists because the scissor needs to end _after_ borders between elements if (closeClipElement) { Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .id = Clay__HashNumber(currentElement->id, rootElement->childrenOrTextContent.children.length + 11).id, .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, }); } dfsBuffer.length--; continue; } // Add children to the DFS buffer if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { dfsBuffer.length += currentElement->childrenOrTextContent.children.length; for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); // Alignment along non layout axis if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { currentElementTreeNode->nextChildOffset.y = currentElement->layoutConfig->padding.top; float whiteSpaceAroundChild = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - childElement->dimensions.height; switch (layoutConfig->childAlignment.y) { case CLAY_ALIGN_Y_TOP: break; case CLAY_ALIGN_Y_CENTER: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild / 2; break; case CLAY_ALIGN_Y_BOTTOM: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild; break; } } else { currentElementTreeNode->nextChildOffset.x = currentElement->layoutConfig->padding.left; float whiteSpaceAroundChild = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - childElement->dimensions.width; switch (layoutConfig->childAlignment.x) { case CLAY_ALIGN_X_LEFT: break; case CLAY_ALIGN_X_CENTER: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild / 2; break; case CLAY_ALIGN_X_RIGHT: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild; break; } } Clay_Vector2 childPosition = { currentElementTreeNode->position.x + currentElementTreeNode->nextChildOffset.x + scrollOffset.x, currentElementTreeNode->position.y + currentElementTreeNode->nextChildOffset.y + scrollOffset.y, }; // DFS buffer elements need to be added in reverse because stack traversal happens backwards uint32_t newNodeIndex = dfsBuffer.length - 1 - i; dfsBuffer.internalArray[newNodeIndex] = CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = childElement, .position = { childPosition.x, childPosition.y }, .nextChildOffset = { .x = (float)childElement->layoutConfig->padding.left, .y = (float)childElement->layoutConfig->padding.top }, }; context->treeNodeVisited.internalArray[newNodeIndex] = false; // Update parent offsets if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { currentElementTreeNode->nextChildOffset.x += childElement->dimensions.width + (float)layoutConfig->childGap; } else { currentElementTreeNode->nextChildOffset.y += childElement->dimensions.height + (float)layoutConfig->childGap; } } } } if (root->clipElementId) { Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .id = Clay__HashNumber(rootElement->id, rootElement->childrenOrTextContent.children.length + 11).id, .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END }); } } } CLAY_WASM_EXPORT("Clay_GetPointerOverIds") CLAY_DLL_EXPORT Clay_ElementIdArray Clay_GetPointerOverIds(void) { return Clay_GetCurrentContext()->pointerOverIds; } #pragma region DebugTools Clay_Color CLAY__DEBUGVIEW_COLOR_1 = {58, 56, 52, 255}; Clay_Color CLAY__DEBUGVIEW_COLOR_2 = {62, 60, 58, 255}; Clay_Color CLAY__DEBUGVIEW_COLOR_3 = {141, 133, 135, 255}; Clay_Color CLAY__DEBUGVIEW_COLOR_4 = {238, 226, 231, 255}; Clay_Color CLAY__DEBUGVIEW_COLOR_SELECTED_ROW = {102, 80, 78, 255}; const int32_t CLAY__DEBUGVIEW_ROW_HEIGHT = 30; const int32_t CLAY__DEBUGVIEW_OUTER_PADDING = 10; const int32_t CLAY__DEBUGVIEW_INDENT_WIDTH = 16; Clay_TextElementConfig Clay__DebugView_TextNameConfig = {.textColor = {238, 226, 231, 255}, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }; Clay_LayoutConfig Clay__DebugView_ScrollViewItemLayoutConfig = CLAY__DEFAULT_STRUCT; typedef struct { Clay_String label; Clay_Color color; } Clay__DebugElementConfigTypeLabelConfig; Clay__DebugElementConfigTypeLabelConfig Clay__DebugGetElementConfigTypeLabel(Clay__ElementConfigType type) { switch (type) { case CLAY__ELEMENT_CONFIG_TYPE_SHARED: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Shared"), {243,134,48,255} }; case CLAY__ELEMENT_CONFIG_TYPE_TEXT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Text"), {105,210,231,255} }; case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Aspect"), {101,149,194,255} }; case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Image"), {121,189,154,255} }; case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Floating"), {250,105,0,255} }; case CLAY__ELEMENT_CONFIG_TYPE_CLIP: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) {CLAY_STRING("Scroll"), {242, 196, 90, 255} }; case CLAY__ELEMENT_CONFIG_TYPE_BORDER: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) {CLAY_STRING("Border"), {108, 91, 123, 255} }; case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Custom"), {11,72,107,255} }; default: break; } return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Error"), {0,0,0,255} }; } typedef struct { int32_t rowCount; int32_t selectedElementRowIndex; } Clay__RenderDebugLayoutData; // Returns row count Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialRootsLength, int32_t highlightedRowIndex) { Clay_Context* context = Clay_GetCurrentContext(); Clay__int32_tArray dfsBuffer = context->reusableElementIndexBuffer; Clay__DebugView_ScrollViewItemLayoutConfig = CLAY__INIT(Clay_LayoutConfig) { .sizing = { .height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT) }, .childGap = 6, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }}; Clay__RenderDebugLayoutData layoutData = CLAY__DEFAULT_STRUCT; uint32_t highlightedElementId = 0; for (int32_t rootIndex = 0; rootIndex < initialRootsLength; ++rootIndex) { dfsBuffer.length = 0; Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); context->treeNodeVisited.internalArray[0] = false; if (rootIndex > 0) { CLAY(CLAY_IDI("Clay__DebugView_EmptyRowOuter", rootIndex), { .layout = { .sizing = {.width = CLAY_SIZING_GROW(0)}, .padding = {CLAY__DEBUGVIEW_INDENT_WIDTH / 2, 0, 0, 0} } }) { CLAY(CLAY_IDI("Clay__DebugView_EmptyRow", rootIndex), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED((float)CLAY__DEBUGVIEW_ROW_HEIGHT) }}, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .top = 1 } } }) {} } layoutData.rowCount++; } while (dfsBuffer.length > 0) { int32_t currentElementIndex = Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1); Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)currentElementIndex); if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && currentElement->childrenOrTextContent.children.length > 0) { Clay__CloseElement(); Clay__CloseElement(); Clay__CloseElement(); } dfsBuffer.length--; continue; } if (highlightedRowIndex == layoutData.rowCount) { if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { context->debugSelectedElementId = currentElement->id; } highlightedElementId = currentElement->id; } context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); bool offscreen = Clay__ElementIsOffscreen(¤tElementData->boundingBox); if (context->debugSelectedElementId == currentElement->id) { layoutData.selectedElementRowIndex = layoutData.rowCount; } CLAY(CLAY_IDI("Clay__DebugView_ElementOuter", currentElement->id), { .layout = Clay__DebugView_ScrollViewItemLayoutConfig }) { // Collapse icon / button if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0)) { CLAY(CLAY_IDI("Clay__DebugView_CollapseElement", currentElement->id), { .layout = { .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = {1, 1, 1, 1, 0} }, }) { CLAY_TEXT((currentElementData && currentElementData->debugData->collapsed) ? CLAY_STRING("+") : CLAY_STRING("-"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); } } else { // Square dot for empty containers CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER } } }) { CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIXED(8), CLAY_SIZING_FIXED(8)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3, .cornerRadius = CLAY_CORNER_RADIUS(2) }) {} } } // Collisions and offscreen info if (currentElementData) { if (currentElementData->debugData->collision) { CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 }}, .border = { .color = {177, 147, 8, 255}, .width = {1, 1, 1, 1, 0} } }) { CLAY_TEXT(CLAY_STRING("Duplicate ID"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 })); } } if (offscreen) { CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { 1, 1, 1, 1, 0} } }) { CLAY_TEXT(CLAY_STRING("Offscreen"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 })); } } } Clay_String idString = context->layoutElementIdStrings.internalArray[currentElementIndex]; if (idString.length > 0) { CLAY_TEXT(idString, offscreen ? CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 }) : &Clay__DebugView_TextNameConfig); } for (int32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, elementConfigIndex); if (elementConfig->type == CLAY__ELEMENT_CONFIG_TYPE_SHARED) { Clay_Color labelColor = {243,134,48,90}; labelColor.a = 90; Clay_Color backgroundColor = elementConfig->config.sharedElementConfig->backgroundColor; Clay_CornerRadius radius = elementConfig->config.sharedElementConfig->cornerRadius; if (backgroundColor.a > 0) { CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = labelColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = labelColor, .width = { 1, 1, 1, 1, 0} } }) { CLAY_TEXT(CLAY_STRING("Color"), CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); } } if (radius.bottomLeft > 0) { CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = labelColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = labelColor, .width = { 1, 1, 1, 1, 0 } } }) { CLAY_TEXT(CLAY_STRING("Radius"), CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); } } continue; } Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(elementConfig->type); Clay_Color backgroundColor = config.color; backgroundColor.a = 90; CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = config.color, .width = { 1, 1, 1, 1, 0 } } }) { CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); } } } // Render the text contents below the element as a non-interactive row if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { layoutData.rowCount++; Clay__TextElementData *textElementData = currentElement->childrenOrTextContent.textElementData; Clay_TextElementConfig *rawTextConfig = offscreen ? CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 }) : &Clay__DebugView_TextNameConfig; CLAY_AUTO_ID({ .layout = { .sizing = { .height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } } }) { CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_INDENT_WIDTH + 16) } } }) {} CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); CLAY_TEXT(textElementData->text.length > 40 ? (CLAY__INIT(Clay_String) { .length = 40, .chars = textElementData->text.chars }) : textElementData->text, rawTextConfig); if (textElementData->text.length > 40) { CLAY_TEXT(CLAY_STRING("..."), rawTextConfig); } CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); } } else if (currentElement->childrenOrTextContent.children.length > 0) { Clay__OpenElement(); Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .padding = { .left = 8 } } }); Clay__OpenElement(); Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .padding = { .left = CLAY__DEBUGVIEW_INDENT_WIDTH }}, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .left = 1 } }}); Clay__OpenElement(); Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } }); } layoutData.rowCount++; if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (currentElementData && currentElementData->debugData->collapsed))) { for (int32_t i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) { Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]); context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked } } } } if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { Clay_ElementId collapseButtonId = Clay__HashString(CLAY_STRING("Clay__DebugView_CollapseElement"), 0); for (int32_t i = (int)context->pointerOverIds.length - 1; i >= 0; i--) { Clay_ElementId *elementId = Clay_ElementIdArray_Get(&context->pointerOverIds, i); if (elementId->baseId == collapseButtonId.baseId) { Clay_LayoutElementHashMapItem *highlightedItem = Clay__GetHashMapItem(elementId->offset); highlightedItem->debugData->collapsed = !highlightedItem->debugData->collapsed; break; } } } if (highlightedElementId) { CLAY(CLAY_ID("Clay__DebugView_ElementHighlight"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .floating = { .parentId = highlightedElementId, .zIndex = 32767, .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID } }) { CLAY(CLAY_ID("Clay__DebugView_ElementHighlightRectangle"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .backgroundColor = Clay__debugViewHighlightColor }) {} } } return layoutData; } void Clay__RenderDebugLayoutSizing(Clay_SizingAxis sizing, Clay_TextElementConfig *infoTextConfig) { Clay_String sizingLabel = CLAY_STRING("GROW"); if (sizing.type == CLAY__SIZING_TYPE_FIT) { sizingLabel = CLAY_STRING("FIT"); } else if (sizing.type == CLAY__SIZING_TYPE_PERCENT) { sizingLabel = CLAY_STRING("PERCENT"); } else if (sizing.type == CLAY__SIZING_TYPE_FIXED) { sizingLabel = CLAY_STRING("FIXED"); } CLAY_TEXT(sizingLabel, infoTextConfig); if (sizing.type == CLAY__SIZING_TYPE_GROW || sizing.type == CLAY__SIZING_TYPE_FIT || sizing.type == CLAY__SIZING_TYPE_FIXED) { CLAY_TEXT(CLAY_STRING("("), infoTextConfig); if (sizing.size.minMax.min != 0) { CLAY_TEXT(CLAY_STRING("min: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(sizing.size.minMax.min), infoTextConfig); if (sizing.size.minMax.max != CLAY__MAXFLOAT) { CLAY_TEXT(CLAY_STRING(", "), infoTextConfig); } } if (sizing.size.minMax.max != CLAY__MAXFLOAT) { CLAY_TEXT(CLAY_STRING("max: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(sizing.size.minMax.max), infoTextConfig); } CLAY_TEXT(CLAY_STRING(")"), infoTextConfig); } else if (sizing.type == CLAY__SIZING_TYPE_PERCENT) { CLAY_TEXT(CLAY_STRING("("), infoTextConfig); CLAY_TEXT(Clay__IntToString(sizing.size.percent * 100), infoTextConfig); CLAY_TEXT(CLAY_STRING("%)"), infoTextConfig); } } void Clay__RenderDebugViewElementConfigHeader(Clay_String elementId, Clay__ElementConfigType type) { Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(type); Clay_Color backgroundColor = config.color; backgroundColor.a = 90; CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(CLAY__DEBUGVIEW_OUTER_PADDING), .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } } }) { CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = config.color, .width = { 1, 1, 1, 1, 0 } } }) { CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); } CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} CLAY_TEXT(elementId, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE })); } } void Clay__RenderDebugViewColor(Clay_Color color, Clay_TextElementConfig *textConfig) { CLAY_AUTO_ID({ .layout = { .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { CLAY_TEXT(CLAY_STRING("{ r: "), textConfig); CLAY_TEXT(Clay__IntToString(color.r), textConfig); CLAY_TEXT(CLAY_STRING(", g: "), textConfig); CLAY_TEXT(Clay__IntToString(color.g), textConfig); CLAY_TEXT(CLAY_STRING(", b: "), textConfig); CLAY_TEXT(Clay__IntToString(color.b), textConfig); CLAY_TEXT(CLAY_STRING(", a: "), textConfig); CLAY_TEXT(Clay__IntToString(color.a), textConfig); CLAY_TEXT(CLAY_STRING(" }"), textConfig); CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_FIXED(10) } } }) {} CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8)} }, .backgroundColor = color, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = CLAY__DEBUGVIEW_COLOR_4, .width = { 1, 1, 1, 1, 0 } } }) {} } } void Clay__RenderDebugViewCornerRadius(Clay_CornerRadius cornerRadius, Clay_TextElementConfig *textConfig) { CLAY_AUTO_ID({ .layout = { .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { CLAY_TEXT(CLAY_STRING("{ topLeft: "), textConfig); CLAY_TEXT(Clay__IntToString(cornerRadius.topLeft), textConfig); CLAY_TEXT(CLAY_STRING(", topRight: "), textConfig); CLAY_TEXT(Clay__IntToString(cornerRadius.topRight), textConfig); CLAY_TEXT(CLAY_STRING(", bottomLeft: "), textConfig); CLAY_TEXT(Clay__IntToString(cornerRadius.bottomLeft), textConfig); CLAY_TEXT(CLAY_STRING(", bottomRight: "), textConfig); CLAY_TEXT(Clay__IntToString(cornerRadius.bottomRight), textConfig); CLAY_TEXT(CLAY_STRING(" }"), textConfig); } } void HandleDebugViewCloseButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData) { Clay_Context* context = Clay_GetCurrentContext(); (void) elementId; (void) pointerInfo; (void) userData; if (pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { context->debugModeEnabled = false; } } void Clay__RenderDebugView(void) { Clay_Context* context = Clay_GetCurrentContext(); Clay_ElementId closeButtonId = Clay__HashString(CLAY_STRING("Clay__DebugViewTopHeaderCloseButtonOuter"), 0); if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { Clay_ElementId *elementId = Clay_ElementIdArray_Get(&context->pointerOverIds, i); if (elementId->id == closeButtonId.id) { context->debugModeEnabled = false; return; } } } uint32_t initialRootsLength = context->layoutElementTreeRoots.length; uint32_t initialElementsLength = context->layoutElements.length; Clay_TextElementConfig *infoTextConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); Clay_TextElementConfig *infoTitleConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); Clay_ElementId scrollId = Clay__HashString(CLAY_STRING("Clay__DebugViewOuterScrollPane"), 0); float scrollYOffset = 0; bool pointerInDebugView = context->pointerInfo.position.y < context->layoutDimensions.height - 300; for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) { Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); if (scrollContainerData->elementId == scrollId.id) { if (!context->externalScrollHandlingEnabled) { scrollYOffset = scrollContainerData->scrollPosition.y; } else { pointerInDebugView = context->pointerInfo.position.y + scrollContainerData->scrollPosition.y < context->layoutDimensions.height - 300; } break; } } int32_t highlightedRow = pointerInDebugView ? (int32_t)((context->pointerInfo.position.y - scrollYOffset) / (float)CLAY__DEBUGVIEW_ROW_HEIGHT) - 1 : -1; if (context->pointerInfo.position.x < context->layoutDimensions.width - (float)Clay__debugViewWidth) { highlightedRow = -1; } Clay__RenderDebugLayoutData layoutData = CLAY__DEFAULT_STRUCT; CLAY(CLAY_ID("Clay__DebugView"), { .layout = { .sizing = { CLAY_SIZING_FIXED((float)Clay__debugViewWidth) , CLAY_SIZING_FIXED(context->layoutDimensions.height) }, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .floating = { .zIndex = 32765, .attachPoints = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_CENTER }, .attachTo = CLAY_ATTACH_TO_ROOT, .clipTo = CLAY_CLIP_TO_ATTACHED_PARENT }, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .bottom = 1 } } }) { CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_2 }) { CLAY_TEXT(CLAY_STRING("Clay Debug Tools"), infoTextConfig); CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} // Close button CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 10), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 10)}, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }, .backgroundColor = {217,91,67,80}, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = { 217,91,67,255 }, .width = { 1, 1, 1, 1, 0 } }, }) { Clay_OnHover(HandleDebugViewCloseButtonInteraction, 0); CLAY_TEXT(CLAY_STRING("x"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); } } CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(1)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3 } ) {} CLAY(scrollId, { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .clip = { .horizontal = true, .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = ((initialElementsLength + initialRootsLength) & 1) == 0 ? CLAY__DEBUGVIEW_COLOR_2 : CLAY__DEBUGVIEW_COLOR_1 }) { Clay_ElementId panelContentsId = Clay__HashString(CLAY_STRING("Clay__DebugViewPaneOuter"), 0); // Element list CLAY(panelContentsId, { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .floating = { .zIndex = 32766, .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, .attachTo = CLAY_ATTACH_TO_PARENT, .clipTo = CLAY_CLIP_TO_ATTACHED_PARENT } }) { CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = { CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { layoutData = Clay__RenderDebugLayoutElementsList((int32_t)initialRootsLength, highlightedRow); } } float contentWidth = Clay__GetHashMapItem(panelContentsId.id)->layoutElement->dimensions.width; CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(contentWidth) }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {} for (int32_t i = 0; i < layoutData.rowCount; i++) { Clay_Color rowColor = (i & 1) == 0 ? CLAY__DEBUGVIEW_COLOR_2 : CLAY__DEBUGVIEW_COLOR_1; if (i == layoutData.selectedElementRowIndex) { rowColor = CLAY__DEBUGVIEW_COLOR_SELECTED_ROW; } if (i == highlightedRow) { rowColor.r *= 1.25f; rowColor.g *= 1.25f; rowColor.b *= 1.25f; } CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = rowColor } ) {} } } } CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(1)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3 }) {} if (context->debugSelectedElementId != 0) { Clay_LayoutElementHashMapItem *selectedItem = Clay__GetHashMapItem(context->debugSelectedElementId); CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(300)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_2 , .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .betweenChildren = 1 } } }) { CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT + 8)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { CLAY_TEXT(CLAY_STRING("Layout Config"), infoTextConfig); CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} if (selectedItem->elementId.stringId.length != 0) { CLAY_TEXT(selectedItem->elementId.stringId, infoTitleConfig); if (selectedItem->elementId.offset != 0) { CLAY_TEXT(CLAY_STRING(" ("), infoTitleConfig); CLAY_TEXT(Clay__IntToString(selectedItem->elementId.offset), infoTitleConfig); CLAY_TEXT(CLAY_STRING(")"), infoTitleConfig); } } } Clay_Padding attributeConfigPadding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 8, 8}; // Clay_LayoutConfig debug info CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { // .boundingBox CLAY_TEXT(CLAY_STRING("Bounding Box"), infoTitleConfig); CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.x), infoTextConfig); CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.y), infoTextConfig); CLAY_TEXT(CLAY_STRING(", width: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.width), infoTextConfig); CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.height), infoTextConfig); CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); } // .layoutDirection CLAY_TEXT(CLAY_STRING("Layout Direction"), infoTitleConfig); Clay_LayoutConfig *layoutConfig = selectedItem->layoutElement->layoutConfig; CLAY_TEXT(layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM ? CLAY_STRING("TOP_TO_BOTTOM") : CLAY_STRING("LEFT_TO_RIGHT"), infoTextConfig); // .sizing CLAY_TEXT(CLAY_STRING("Sizing"), infoTitleConfig); CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { CLAY_TEXT(CLAY_STRING("width: "), infoTextConfig); Clay__RenderDebugLayoutSizing(layoutConfig->sizing.width, infoTextConfig); } CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { CLAY_TEXT(CLAY_STRING("height: "), infoTextConfig); Clay__RenderDebugLayoutSizing(layoutConfig->sizing.height, infoTextConfig); } // .padding CLAY_TEXT(CLAY_STRING("Padding"), infoTitleConfig); CLAY(CLAY_ID("Clay__DebugViewElementInfoPadding"), { }) { CLAY_TEXT(CLAY_STRING("{ left: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(layoutConfig->padding.left), infoTextConfig); CLAY_TEXT(CLAY_STRING(", right: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(layoutConfig->padding.right), infoTextConfig); CLAY_TEXT(CLAY_STRING(", top: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(layoutConfig->padding.top), infoTextConfig); CLAY_TEXT(CLAY_STRING(", bottom: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(layoutConfig->padding.bottom), infoTextConfig); CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); } // .childGap CLAY_TEXT(CLAY_STRING("Child Gap"), infoTitleConfig); CLAY_TEXT(Clay__IntToString(layoutConfig->childGap), infoTextConfig); // .childAlignment CLAY_TEXT(CLAY_STRING("Child Alignment"), infoTitleConfig); CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); Clay_String alignX = CLAY_STRING("LEFT"); if (layoutConfig->childAlignment.x == CLAY_ALIGN_X_CENTER) { alignX = CLAY_STRING("CENTER"); } else if (layoutConfig->childAlignment.x == CLAY_ALIGN_X_RIGHT) { alignX = CLAY_STRING("RIGHT"); } CLAY_TEXT(alignX, infoTextConfig); CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); Clay_String alignY = CLAY_STRING("TOP"); if (layoutConfig->childAlignment.y == CLAY_ALIGN_Y_CENTER) { alignY = CLAY_STRING("CENTER"); } else if (layoutConfig->childAlignment.y == CLAY_ALIGN_Y_BOTTOM) { alignY = CLAY_STRING("BOTTOM"); } CLAY_TEXT(alignY, infoTextConfig); CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); } } for (int32_t elementConfigIndex = 0; elementConfigIndex < selectedItem->layoutElement->elementConfigs.length; ++elementConfigIndex) { Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(&selectedItem->layoutElement->elementConfigs, elementConfigIndex); Clay__RenderDebugViewElementConfigHeader(selectedItem->elementId.stringId, elementConfig->type); switch (elementConfig->type) { case CLAY__ELEMENT_CONFIG_TYPE_SHARED: { Clay_SharedElementConfig *sharedConfig = elementConfig->config.sharedElementConfig; CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) { // .backgroundColor CLAY_TEXT(CLAY_STRING("Background Color"), infoTitleConfig); Clay__RenderDebugViewColor(sharedConfig->backgroundColor, infoTextConfig); // .cornerRadius CLAY_TEXT(CLAY_STRING("Corner Radius"), infoTitleConfig); Clay__RenderDebugViewCornerRadius(sharedConfig->cornerRadius, infoTextConfig); } break; } case CLAY__ELEMENT_CONFIG_TYPE_TEXT: { Clay_TextElementConfig *textConfig = elementConfig->config.textElementConfig; CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { // .fontSize CLAY_TEXT(CLAY_STRING("Font Size"), infoTitleConfig); CLAY_TEXT(Clay__IntToString(textConfig->fontSize), infoTextConfig); // .fontId CLAY_TEXT(CLAY_STRING("Font ID"), infoTitleConfig); CLAY_TEXT(Clay__IntToString(textConfig->fontId), infoTextConfig); // .lineHeight CLAY_TEXT(CLAY_STRING("Line Height"), infoTitleConfig); CLAY_TEXT(textConfig->lineHeight == 0 ? CLAY_STRING("auto") : Clay__IntToString(textConfig->lineHeight), infoTextConfig); // .letterSpacing CLAY_TEXT(CLAY_STRING("Letter Spacing"), infoTitleConfig); CLAY_TEXT(Clay__IntToString(textConfig->letterSpacing), infoTextConfig); // .wrapMode CLAY_TEXT(CLAY_STRING("Wrap Mode"), infoTitleConfig); Clay_String wrapMode = CLAY_STRING("WORDS"); if (textConfig->wrapMode == CLAY_TEXT_WRAP_NONE) { wrapMode = CLAY_STRING("NONE"); } else if (textConfig->wrapMode == CLAY_TEXT_WRAP_NEWLINES) { wrapMode = CLAY_STRING("NEWLINES"); } CLAY_TEXT(wrapMode, infoTextConfig); // .textAlignment CLAY_TEXT(CLAY_STRING("Text Alignment"), infoTitleConfig); Clay_String textAlignment = CLAY_STRING("LEFT"); if (textConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { textAlignment = CLAY_STRING("CENTER"); } else if (textConfig->textAlignment == CLAY_TEXT_ALIGN_RIGHT) { textAlignment = CLAY_STRING("RIGHT"); } CLAY_TEXT(textAlignment, infoTextConfig); // .textColor CLAY_TEXT(CLAY_STRING("Text Color"), infoTitleConfig); Clay__RenderDebugViewColor(textConfig->textColor, infoTextConfig); } break; } case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: { Clay_AspectRatioElementConfig *aspectRatioConfig = elementConfig->config.aspectRatioElementConfig; CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatioBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { CLAY_TEXT(CLAY_STRING("Aspect Ratio"), infoTitleConfig); // Aspect Ratio CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatio"), { }) { CLAY_TEXT(Clay__IntToString(aspectRatioConfig->aspectRatio), infoTextConfig); CLAY_TEXT(CLAY_STRING("."), infoTextConfig); float frac = aspectRatioConfig->aspectRatio - (int)(aspectRatioConfig->aspectRatio); frac *= 100; if ((int)frac < 10) { CLAY_TEXT(CLAY_STRING("0"), infoTextConfig); } CLAY_TEXT(Clay__IntToString(frac), infoTextConfig); } } break; } case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { Clay_ImageElementConfig *imageConfig = elementConfig->config.imageElementConfig; Clay_AspectRatioElementConfig aspectConfig = { 1 }; if (Clay__ElementHasConfig(selectedItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT)) { aspectConfig = *Clay__FindElementConfigWithType(selectedItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; } CLAY(CLAY_ID("Clay__DebugViewElementInfoImageBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { // Image Preview CLAY_TEXT(CLAY_STRING("Preview"), infoTitleConfig); CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(64, 128), .height = CLAY_SIZING_GROW(64, 128) }}, .aspectRatio = aspectConfig, .image = *imageConfig }) {} } break; } case CLAY__ELEMENT_CONFIG_TYPE_CLIP: { Clay_ClipElementConfig *clipConfig = elementConfig->config.clipElementConfig; CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { // .vertical CLAY_TEXT(CLAY_STRING("Vertical"), infoTitleConfig); CLAY_TEXT(clipConfig->vertical ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); // .horizontal CLAY_TEXT(CLAY_STRING("Horizontal"), infoTitleConfig); CLAY_TEXT(clipConfig->horizontal ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); } break; } case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: { Clay_FloatingElementConfig *floatingConfig = elementConfig->config.floatingElementConfig; CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { // .offset CLAY_TEXT(CLAY_STRING("Offset"), infoTitleConfig); CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(floatingConfig->offset.x), infoTextConfig); CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(floatingConfig->offset.y), infoTextConfig); CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); } // .expand CLAY_TEXT(CLAY_STRING("Expand"), infoTitleConfig); CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { CLAY_TEXT(CLAY_STRING("{ width: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(floatingConfig->expand.width), infoTextConfig); CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(floatingConfig->expand.height), infoTextConfig); CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); } // .zIndex CLAY_TEXT(CLAY_STRING("z-index"), infoTitleConfig); CLAY_TEXT(Clay__IntToString(floatingConfig->zIndex), infoTextConfig); // .parentId CLAY_TEXT(CLAY_STRING("Parent"), infoTitleConfig); Clay_LayoutElementHashMapItem *hashItem = Clay__GetHashMapItem(floatingConfig->parentId); CLAY_TEXT(hashItem->elementId.stringId, infoTextConfig); // .attachPoints CLAY_TEXT(CLAY_STRING("Attach Points"), infoTitleConfig); CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { CLAY_TEXT(CLAY_STRING("{ element: "), infoTextConfig); Clay_String attachPointElement = CLAY_STRING("LEFT_TOP"); if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_CENTER) { attachPointElement = CLAY_STRING("LEFT_CENTER"); } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_BOTTOM) { attachPointElement = CLAY_STRING("LEFT_BOTTOM"); } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_TOP) { attachPointElement = CLAY_STRING("CENTER_TOP"); } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_CENTER) { attachPointElement = CLAY_STRING("CENTER_CENTER"); } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_BOTTOM) { attachPointElement = CLAY_STRING("CENTER_BOTTOM"); } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_TOP) { attachPointElement = CLAY_STRING("RIGHT_TOP"); } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_CENTER) { attachPointElement = CLAY_STRING("RIGHT_CENTER"); } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { attachPointElement = CLAY_STRING("RIGHT_BOTTOM"); } CLAY_TEXT(attachPointElement, infoTextConfig); Clay_String attachPointParent = CLAY_STRING("LEFT_TOP"); if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_CENTER) { attachPointParent = CLAY_STRING("LEFT_CENTER"); } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_BOTTOM) { attachPointParent = CLAY_STRING("LEFT_BOTTOM"); } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_TOP) { attachPointParent = CLAY_STRING("CENTER_TOP"); } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_CENTER) { attachPointParent = CLAY_STRING("CENTER_CENTER"); } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_BOTTOM) { attachPointParent = CLAY_STRING("CENTER_BOTTOM"); } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_TOP) { attachPointParent = CLAY_STRING("RIGHT_TOP"); } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_CENTER) { attachPointParent = CLAY_STRING("RIGHT_CENTER"); } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { attachPointParent = CLAY_STRING("RIGHT_BOTTOM"); } CLAY_TEXT(CLAY_STRING(", parent: "), infoTextConfig); CLAY_TEXT(attachPointParent, infoTextConfig); CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); } // .pointerCaptureMode CLAY_TEXT(CLAY_STRING("Pointer Capture Mode"), infoTitleConfig); Clay_String pointerCaptureMode = CLAY_STRING("NONE"); if (floatingConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH) { pointerCaptureMode = CLAY_STRING("PASSTHROUGH"); } CLAY_TEXT(pointerCaptureMode, infoTextConfig); // .attachTo CLAY_TEXT(CLAY_STRING("Attach To"), infoTitleConfig); Clay_String attachTo = CLAY_STRING("NONE"); if (floatingConfig->attachTo == CLAY_ATTACH_TO_PARENT) { attachTo = CLAY_STRING("PARENT"); } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) { attachTo = CLAY_STRING("ELEMENT_WITH_ID"); } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ROOT) { attachTo = CLAY_STRING("ROOT"); } CLAY_TEXT(attachTo, infoTextConfig); // .clipTo CLAY_TEXT(CLAY_STRING("Clip To"), infoTitleConfig); Clay_String clipTo = CLAY_STRING("ATTACHED_PARENT"); if (floatingConfig->clipTo == CLAY_CLIP_TO_NONE) { clipTo = CLAY_STRING("NONE"); } CLAY_TEXT(clipTo, infoTextConfig); } break; } case CLAY__ELEMENT_CONFIG_TYPE_BORDER: { Clay_BorderElementConfig *borderConfig = elementConfig->config.borderElementConfig; CLAY(CLAY_ID("Clay__DebugViewElementInfoBorderBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { CLAY_TEXT(CLAY_STRING("Border Widths"), infoTitleConfig); CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { CLAY_TEXT(CLAY_STRING("{ left: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(borderConfig->width.left), infoTextConfig); CLAY_TEXT(CLAY_STRING(", right: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(borderConfig->width.right), infoTextConfig); CLAY_TEXT(CLAY_STRING(", top: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(borderConfig->width.top), infoTextConfig); CLAY_TEXT(CLAY_STRING(", bottom: "), infoTextConfig); CLAY_TEXT(Clay__IntToString(borderConfig->width.bottom), infoTextConfig); CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); } // .textColor CLAY_TEXT(CLAY_STRING("Border Color"), infoTitleConfig); Clay__RenderDebugViewColor(borderConfig->color, infoTextConfig); } break; } case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: default: break; } } } } else { CLAY(CLAY_ID("Clay__DebugViewWarningsScrollPane"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(300)}, .childGap = 6, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_2, .clip = { .horizontal = true, .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { Clay_TextElementConfig *warningConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); CLAY(CLAY_ID("Clay__DebugViewWarningItemHeader"), { .layout = { .sizing = {.height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childGap = 8, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { CLAY_TEXT(CLAY_STRING("Warnings"), warningConfig); } CLAY(CLAY_ID("Clay__DebugViewWarningsTopBorder"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(1)} }, .backgroundColor = {200, 200, 200, 255} }) {} int32_t previousWarningsLength = context->warnings.length; for (int32_t i = 0; i < previousWarningsLength; i++) { Clay__Warning warning = context->warnings.internalArray[i]; CLAY(CLAY_IDI("Clay__DebugViewWarningItem", i), { .layout = { .sizing = {.height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childGap = 8, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { CLAY_TEXT(warning.baseMessage, warningConfig); if (warning.dynamicMessage.length > 0) { CLAY_TEXT(warning.dynamicMessage, warningConfig); } } } } } } } #pragma endregion uint32_t Clay__debugViewWidth = 400; Clay_Color Clay__debugViewHighlightColor = { 168, 66, 28, 100 }; Clay__WarningArray Clay__WarningArray_Allocate_Arena(int32_t capacity, Clay_Arena *arena) { size_t totalSizeBytes = capacity * sizeof(Clay_String); Clay__WarningArray array = {.capacity = capacity, .length = 0}; uintptr_t nextAllocOffset = arena->nextAllocation + (64 - (arena->nextAllocation % 64)); if (nextAllocOffset + totalSizeBytes <= arena->capacity) { array.internalArray = (Clay__Warning*)((uintptr_t)arena->memory + (uintptr_t)nextAllocOffset); arena->nextAllocation = nextAllocOffset + totalSizeBytes; } else { Clay__currentContext->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, .errorText = CLAY_STRING("Clay attempted to allocate memory in its arena, but ran out of capacity. Try increasing the capacity of the arena passed to Clay_Initialize()"), .userData = Clay__currentContext->errorHandler.userData }); } return array; } Clay__Warning *Clay__WarningArray_Add(Clay__WarningArray *array, Clay__Warning item) { if (array->length < array->capacity) { array->internalArray[array->length++] = item; return &array->internalArray[array->length - 1]; } return &CLAY__WARNING_DEFAULT; } void* Clay__Array_Allocate_Arena(int32_t capacity, uint32_t itemSize, Clay_Arena *arena) { size_t totalSizeBytes = capacity * itemSize; uintptr_t nextAllocOffset = arena->nextAllocation + ((64 - (arena->nextAllocation % 64)) & 63); if (nextAllocOffset + totalSizeBytes <= arena->capacity) { arena->nextAllocation = nextAllocOffset + totalSizeBytes; return (void*)((uintptr_t)arena->memory + (uintptr_t)nextAllocOffset); } else { Clay__currentContext->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, .errorText = CLAY_STRING("Clay attempted to allocate memory in its arena, but ran out of capacity. Try increasing the capacity of the arena passed to Clay_Initialize()"), .userData = Clay__currentContext->errorHandler.userData }); } return CLAY__NULL; } bool Clay__Array_RangeCheck(int32_t index, int32_t length) { if (index < length && index >= 0) { return true; } Clay_Context* context = Clay_GetCurrentContext(); context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_INTERNAL_ERROR, .errorText = CLAY_STRING("Clay attempted to make an out of bounds array access. This is an internal error and is likely a bug."), .userData = context->errorHandler.userData }); return false; } bool Clay__Array_AddCapacityCheck(int32_t length, int32_t capacity) { if (length < capacity) { return true; } Clay_Context* context = Clay_GetCurrentContext(); context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_INTERNAL_ERROR, .errorText = CLAY_STRING("Clay attempted to make an out of bounds array access. This is an internal error and is likely a bug."), .userData = context->errorHandler.userData }); return false; } // PUBLIC API FROM HERE --------------------------------------- CLAY_WASM_EXPORT("Clay_MinMemorySize") uint32_t Clay_MinMemorySize(void) { Clay_Context fakeContext = { .maxElementCount = Clay__defaultMaxElementCount, .maxMeasureTextCacheWordCount = Clay__defaultMaxMeasureTextWordCacheCount, .internalArena = { .capacity = SIZE_MAX, .memory = NULL, } }; Clay_Context* currentContext = Clay_GetCurrentContext(); if (currentContext) { fakeContext.maxElementCount = currentContext->maxElementCount; fakeContext.maxMeasureTextCacheWordCount = currentContext->maxMeasureTextCacheWordCount; } // Reserve space in the arena for the context, important for calculating min memory size correctly Clay__Context_Allocate_Arena(&fakeContext.internalArena); Clay__InitializePersistentMemory(&fakeContext); Clay__InitializeEphemeralMemory(&fakeContext); return (uint32_t)fakeContext.internalArena.nextAllocation + 128; } CLAY_WASM_EXPORT("Clay_CreateArenaWithCapacityAndMemory") Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory) { Clay_Arena arena = { .capacity = capacity, .memory = (char *)memory }; return arena; } #ifndef CLAY_WASM void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData) { Clay_Context* context = Clay_GetCurrentContext(); Clay__MeasureText = measureTextFunction; context->measureTextUserData = userData; } void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData) { Clay_Context* context = Clay_GetCurrentContext(); Clay__QueryScrollOffset = queryScrollOffsetFunction; context->queryScrollOffsetUserData = userData; } #endif CLAY_WASM_EXPORT("Clay_SetLayoutDimensions") void Clay_SetLayoutDimensions(Clay_Dimensions dimensions) { Clay_GetCurrentContext()->layoutDimensions = dimensions; } CLAY_WASM_EXPORT("Clay_SetPointerState") void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { Clay_Context* context = Clay_GetCurrentContext(); if (context->booleanWarnings.maxElementsExceeded) { return; } context->pointerInfo.position = position; context->pointerOverIds.length = 0; Clay__int32_tArray dfsBuffer = context->layoutElementChildrenBuffer; for (int32_t rootIndex = context->layoutElementTreeRoots.length - 1; rootIndex >= 0; --rootIndex) { dfsBuffer.length = 0; Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); context->treeNodeVisited.internalArray[0] = false; bool found = false; while (dfsBuffer.length > 0) { if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { dfsBuffer.length--; continue; } context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1)); Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO think of a way around this, maybe the fact that it's essentially a binary tree limits the cost, but the worst case is not great int32_t clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(currentElement - context->layoutElements.internalArray)); Clay_LayoutElementHashMapItem *clipItem = Clay__GetHashMapItem(clipElementId); if (mapItem) { Clay_BoundingBox elementBox = mapItem->boundingBox; elementBox.x -= root->pointerOffset.x; elementBox.y -= root->pointerOffset.y; if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)) || context->externalScrollHandlingEnabled)) { if (mapItem->onHoverFunction) { mapItem->onHoverFunction(mapItem->elementId, context->pointerInfo, mapItem->hoverFunctionUserData); } Clay_ElementIdArray_Add(&context->pointerOverIds, mapItem->elementId); found = true; } if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { dfsBuffer.length--; continue; } for (int32_t i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) { Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]); context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked } } else { dfsBuffer.length--; } } Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, root->layoutElementIndex); if (found && Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING) && Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_CAPTURE) { break; } } if (isPointerDown) { if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { context->pointerInfo.state = CLAY_POINTER_DATA_PRESSED; } else if (context->pointerInfo.state != CLAY_POINTER_DATA_PRESSED) { context->pointerInfo.state = CLAY_POINTER_DATA_PRESSED_THIS_FRAME; } } else { if (context->pointerInfo.state == CLAY_POINTER_DATA_RELEASED_THIS_FRAME) { context->pointerInfo.state = CLAY_POINTER_DATA_RELEASED; } else if (context->pointerInfo.state != CLAY_POINTER_DATA_RELEASED) { context->pointerInfo.state = CLAY_POINTER_DATA_RELEASED_THIS_FRAME; } } } CLAY_WASM_EXPORT("Clay_Initialize") Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler) { // Cacheline align memory passed in uintptr_t baseOffset = 64 - ((uintptr_t)arena.memory % 64); baseOffset = baseOffset == 64 ? 0 : baseOffset; arena.memory += baseOffset; Clay_Context *context = Clay__Context_Allocate_Arena(&arena); if (context == NULL) return NULL; // DEFAULTS Clay_Context *oldContext = Clay_GetCurrentContext(); *context = CLAY__INIT(Clay_Context) { .maxElementCount = oldContext ? oldContext->maxElementCount : Clay__defaultMaxElementCount, .maxMeasureTextCacheWordCount = oldContext ? oldContext->maxMeasureTextCacheWordCount : Clay__defaultMaxMeasureTextWordCacheCount, .errorHandler = errorHandler.errorHandlerFunction ? errorHandler : CLAY__INIT(Clay_ErrorHandler) { Clay__ErrorHandlerFunctionDefault, 0 }, .layoutDimensions = layoutDimensions, .internalArena = arena, }; Clay_SetCurrentContext(context); Clay__InitializePersistentMemory(context); Clay__InitializeEphemeralMemory(context); for (int32_t i = 0; i < context->layoutElementsHashMap.capacity; ++i) { context->layoutElementsHashMap.internalArray[i] = -1; } for (int32_t i = 0; i < context->measureTextHashMap.capacity; ++i) { context->measureTextHashMap.internalArray[i] = 0; } context->measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element" context->layoutDimensions = layoutDimensions; return context; } CLAY_WASM_EXPORT("Clay_GetCurrentContext") Clay_Context* Clay_GetCurrentContext(void) { return Clay__currentContext; } CLAY_WASM_EXPORT("Clay_SetCurrentContext") void Clay_SetCurrentContext(Clay_Context* context) { Clay__currentContext = context; } CLAY_WASM_EXPORT("Clay_GetScrollOffset") Clay_Vector2 Clay_GetScrollOffset(void) { Clay_Context* context = Clay_GetCurrentContext(); if (context->booleanWarnings.maxElementsExceeded) { return CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); // If the element has no id attached at this point, we need to generate one if (openLayoutElement->id == 0) { Clay__GenerateIdForAnonymousElement(openLayoutElement); } for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); if (mapping->layoutElement == openLayoutElement) { return mapping->scrollPosition; } } return CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; } CLAY_WASM_EXPORT("Clay_UpdateScrollContainers") void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime) { Clay_Context* context = Clay_GetCurrentContext(); bool isPointerActive = enableDragScrolling && (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED || context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME); // Don't apply scroll events to ancestors of the inner element int32_t highestPriorityElementIndex = -1; Clay__ScrollContainerDataInternal *highestPriorityScrollData = CLAY__NULL; for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { Clay__ScrollContainerDataInternal *scrollData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); if (!scrollData->openThisFrame) { Clay__ScrollContainerDataInternalArray_RemoveSwapback(&context->scrollContainerDatas, i); continue; } scrollData->openThisFrame = false; Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(scrollData->elementId); // Element isn't rendered this frame but scroll offset has been retained if (!hashMapItem) { Clay__ScrollContainerDataInternalArray_RemoveSwapback(&context->scrollContainerDatas, i); continue; } // Touch / click is released if (!isPointerActive && scrollData->pointerScrollActive) { float xDiff = scrollData->scrollPosition.x - scrollData->scrollOrigin.x; if (xDiff < -10 || xDiff > 10) { scrollData->scrollMomentum.x = (scrollData->scrollPosition.x - scrollData->scrollOrigin.x) / (scrollData->momentumTime * 25); } float yDiff = scrollData->scrollPosition.y - scrollData->scrollOrigin.y; if (yDiff < -10 || yDiff > 10) { scrollData->scrollMomentum.y = (scrollData->scrollPosition.y - scrollData->scrollOrigin.y) / (scrollData->momentumTime * 25); } scrollData->pointerScrollActive = false; scrollData->pointerOrigin = CLAY__INIT(Clay_Vector2){0,0}; scrollData->scrollOrigin = CLAY__INIT(Clay_Vector2){0,0}; scrollData->momentumTime = 0; } // Apply existing momentum scrollData->scrollPosition.x += scrollData->scrollMomentum.x; scrollData->scrollMomentum.x *= 0.95f; bool scrollOccurred = scrollDelta.x != 0 || scrollDelta.y != 0; if ((scrollData->scrollMomentum.x > -0.1f && scrollData->scrollMomentum.x < 0.1f) || scrollOccurred) { scrollData->scrollMomentum.x = 0; } scrollData->scrollPosition.x = CLAY__MIN(CLAY__MAX(scrollData->scrollPosition.x, -(CLAY__MAX(scrollData->contentSize.width - scrollData->layoutElement->dimensions.width, 0))), 0); scrollData->scrollPosition.y += scrollData->scrollMomentum.y; scrollData->scrollMomentum.y *= 0.95f; if ((scrollData->scrollMomentum.y > -0.1f && scrollData->scrollMomentum.y < 0.1f) || scrollOccurred) { scrollData->scrollMomentum.y = 0; } scrollData->scrollPosition.y = CLAY__MIN(CLAY__MAX(scrollData->scrollPosition.y, -(CLAY__MAX(scrollData->contentSize.height - scrollData->layoutElement->dimensions.height, 0))), 0); for (int32_t j = 0; j < context->pointerOverIds.length; ++j) { // TODO n & m are small here but this being n*m gives me the creeps if (scrollData->layoutElement->id == Clay_ElementIdArray_Get(&context->pointerOverIds, j)->id) { highestPriorityElementIndex = j; highestPriorityScrollData = scrollData; } } } if (highestPriorityElementIndex > -1 && highestPriorityScrollData) { Clay_LayoutElement *scrollElement = highestPriorityScrollData->layoutElement; Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(scrollElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; bool canScrollVertically = clipConfig->vertical && highestPriorityScrollData->contentSize.height > scrollElement->dimensions.height; bool canScrollHorizontally = clipConfig->horizontal && highestPriorityScrollData->contentSize.width > scrollElement->dimensions.width; // Handle wheel scroll if (canScrollVertically) { highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollPosition.y + scrollDelta.y * 10; } if (canScrollHorizontally) { highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollPosition.x + scrollDelta.x * 10; } // Handle click / touch scroll if (isPointerActive) { highestPriorityScrollData->scrollMomentum = CLAY__INIT(Clay_Vector2)CLAY__DEFAULT_STRUCT; if (!highestPriorityScrollData->pointerScrollActive) { highestPriorityScrollData->pointerOrigin = context->pointerInfo.position; highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition; highestPriorityScrollData->pointerScrollActive = true; } else { float scrollDeltaX = 0, scrollDeltaY = 0; if (canScrollHorizontally) { float oldXScrollPosition = highestPriorityScrollData->scrollPosition.x; highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollOrigin.x + (context->pointerInfo.position.x - highestPriorityScrollData->pointerOrigin.x); highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - highestPriorityScrollData->boundingBox.width)); scrollDeltaX = highestPriorityScrollData->scrollPosition.x - oldXScrollPosition; } if (canScrollVertically) { float oldYScrollPosition = highestPriorityScrollData->scrollPosition.y; highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollOrigin.y + (context->pointerInfo.position.y - highestPriorityScrollData->pointerOrigin.y); highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - highestPriorityScrollData->boundingBox.height)); scrollDeltaY = highestPriorityScrollData->scrollPosition.y - oldYScrollPosition; } if (scrollDeltaX > -0.1f && scrollDeltaX < 0.1f && scrollDeltaY > -0.1f && scrollDeltaY < 0.1f && highestPriorityScrollData->momentumTime > 0.15f) { highestPriorityScrollData->momentumTime = 0; highestPriorityScrollData->pointerOrigin = context->pointerInfo.position; highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition; } else { highestPriorityScrollData->momentumTime += deltaTime; } } } // Clamp any changes to scroll position to the maximum size of the contents if (canScrollVertically) { highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - scrollElement->dimensions.height)); } if (canScrollHorizontally) { highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - scrollElement->dimensions.width)); } } } CLAY_WASM_EXPORT("Clay_BeginLayout") void Clay_BeginLayout(void) { Clay_Context* context = Clay_GetCurrentContext(); Clay__InitializeEphemeralMemory(context); context->generation++; context->dynamicElementIndex = 0; // Set up the root container that covers the entire window Clay_Dimensions rootDimensions = {context->layoutDimensions.width, context->layoutDimensions.height}; if (context->debugModeEnabled) { rootDimensions.width -= (float)Clay__debugViewWidth; } context->booleanWarnings = CLAY__INIT(Clay_BooleanWarnings) CLAY__DEFAULT_STRUCT; Clay__OpenElementWithId(CLAY_ID("Clay__RootContainer")); Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .sizing = {CLAY_SIZING_FIXED((rootDimensions.width)), CLAY_SIZING_FIXED(rootDimensions.height)} } }); Clay__int32_tArray_Add(&context->openLayoutElementStack, 0); Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { .layoutElementIndex = 0 }); } CLAY_WASM_EXPORT("Clay_EndLayout") Clay_RenderCommandArray Clay_EndLayout(void) { Clay_Context* context = Clay_GetCurrentContext(); Clay__CloseElement(); bool elementsExceededBeforeDebugView = context->booleanWarnings.maxElementsExceeded; if (context->debugModeEnabled && !elementsExceededBeforeDebugView) { context->warningsEnabled = false; Clay__RenderDebugView(); context->warningsEnabled = true; } if (context->booleanWarnings.maxElementsExceeded) { Clay_String message; if (!elementsExceededBeforeDebugView) { message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount after adding the debug-view to the layout."); } else { message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount"); } Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, .renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .textColor = {255, 0, 0, 255}, .fontSize = 16 } }, .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT }); } if (context->openLayoutElementStack.length > 1) { context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_UNBALANCED_OPEN_CLOSE, .errorText = CLAY_STRING("There were still open layout elements when EndLayout was called. This results from an unequal number of calls to Clay__OpenElement and Clay__CloseElement."), .userData = context->errorHandler.userData }); } Clay__CalculateFinalLayout(); return context->renderCommands; } CLAY_WASM_EXPORT("Clay_GetElementId") Clay_ElementId Clay_GetElementId(Clay_String idString) { return Clay__HashString(idString, 0); } CLAY_WASM_EXPORT("Clay_GetElementIdWithIndex") Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index) { return Clay__HashStringWithOffset(idString, index, 0); } bool Clay_Hovered(void) { Clay_Context* context = Clay_GetCurrentContext(); if (context->booleanWarnings.maxElementsExceeded) { return false; } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); // If the element has no id attached at this point, we need to generate one if (openLayoutElement->id == 0) { Clay__GenerateIdForAnonymousElement(openLayoutElement); } for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { if (Clay_ElementIdArray_Get(&context->pointerOverIds, i)->id == openLayoutElement->id) { return true; } } return false; } void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData), void *userData) { Clay_Context* context = Clay_GetCurrentContext(); if (context->booleanWarnings.maxElementsExceeded) { return; } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); if (openLayoutElement->id == 0) { Clay__GenerateIdForAnonymousElement(openLayoutElement); } Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(openLayoutElement->id); hashMapItem->onHoverFunction = onHoverFunction; hashMapItem->hoverFunctionUserData = userData; } CLAY_WASM_EXPORT("Clay_PointerOver") bool Clay_PointerOver(Clay_ElementId elementId) { // TODO return priority for separating multiple results Clay_Context* context = Clay_GetCurrentContext(); for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { if (Clay_ElementIdArray_Get(&context->pointerOverIds, i)->id == elementId.id) { return true; } } return false; } CLAY_WASM_EXPORT("Clay_GetScrollContainerData") Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id) { Clay_Context* context = Clay_GetCurrentContext(); for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) { Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); if (scrollContainerData->elementId == id.id) { Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(scrollContainerData->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; if (!clipElementConfig) { // This can happen on the first frame before a scroll container is declared return CLAY__INIT(Clay_ScrollContainerData) CLAY__DEFAULT_STRUCT; } return CLAY__INIT(Clay_ScrollContainerData) { .scrollPosition = &scrollContainerData->scrollPosition, .scrollContainerDimensions = { scrollContainerData->boundingBox.width, scrollContainerData->boundingBox.height }, .contentDimensions = scrollContainerData->contentSize, .config = *clipElementConfig, .found = true }; } } return CLAY__INIT(Clay_ScrollContainerData) CLAY__DEFAULT_STRUCT; } CLAY_WASM_EXPORT("Clay_GetElementData") Clay_ElementData Clay_GetElementData(Clay_ElementId id){ Clay_LayoutElementHashMapItem * item = Clay__GetHashMapItem(id.id); if(item == &Clay_LayoutElementHashMapItem_DEFAULT) { return CLAY__INIT(Clay_ElementData) CLAY__DEFAULT_STRUCT; } return CLAY__INIT(Clay_ElementData){ .boundingBox = item->boundingBox, .found = true }; } CLAY_WASM_EXPORT("Clay_SetDebugModeEnabled") void Clay_SetDebugModeEnabled(bool enabled) { Clay_Context* context = Clay_GetCurrentContext(); context->debugModeEnabled = enabled; } CLAY_WASM_EXPORT("Clay_IsDebugModeEnabled") bool Clay_IsDebugModeEnabled(void) { Clay_Context* context = Clay_GetCurrentContext(); return context->debugModeEnabled; } CLAY_WASM_EXPORT("Clay_SetCullingEnabled") void Clay_SetCullingEnabled(bool enabled) { Clay_Context* context = Clay_GetCurrentContext(); context->disableCulling = !enabled; } CLAY_WASM_EXPORT("Clay_SetExternalScrollHandlingEnabled") void Clay_SetExternalScrollHandlingEnabled(bool enabled) { Clay_Context* context = Clay_GetCurrentContext(); context->externalScrollHandlingEnabled = enabled; } CLAY_WASM_EXPORT("Clay_GetMaxElementCount") int32_t Clay_GetMaxElementCount(void) { Clay_Context* context = Clay_GetCurrentContext(); return context->maxElementCount; } CLAY_WASM_EXPORT("Clay_SetMaxElementCount") void Clay_SetMaxElementCount(int32_t maxElementCount) { Clay_Context* context = Clay_GetCurrentContext(); if (context) { context->maxElementCount = maxElementCount; } else { Clay__defaultMaxElementCount = maxElementCount; // TODO: Fix this Clay__defaultMaxMeasureTextWordCacheCount = maxElementCount * 2; } } CLAY_WASM_EXPORT("Clay_GetMaxMeasureTextCacheWordCount") int32_t Clay_GetMaxMeasureTextCacheWordCount(void) { Clay_Context* context = Clay_GetCurrentContext(); return context->maxMeasureTextCacheWordCount; } CLAY_WASM_EXPORT("Clay_SetMaxMeasureTextCacheWordCount") void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount) { Clay_Context* context = Clay_GetCurrentContext(); if (context) { Clay__currentContext->maxMeasureTextCacheWordCount = maxMeasureTextCacheWordCount; } else { Clay__defaultMaxMeasureTextWordCacheCount = maxMeasureTextCacheWordCount; // TODO: Fix this } } CLAY_WASM_EXPORT("Clay_ResetMeasureTextCache") void Clay_ResetMeasureTextCache(void) { Clay_Context* context = Clay_GetCurrentContext(); context->measureTextHashMapInternal.length = 0; context->measureTextHashMapInternalFreeList.length = 0; context->measureTextHashMap.length = 0; context->measuredWords.length = 0; context->measuredWordsFreeList.length = 0; for (int32_t i = 0; i < context->measureTextHashMap.capacity; ++i) { context->measureTextHashMap.internalArray[i] = 0; } context->measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element" } #endif // CLAY_IMPLEMENTATION /* LICENSE zlib/libpng license Copyright (c) 2024 Nic Barker This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ ================================================ FILE: cmake/FindCairo.cmake ================================================ # Defines: # CAIRO_FOUND - System has Cairo # CAIRO_INCLUDE_DIRS - Cairo include directories # CAIRO_LIBRARY - Cairo library # Cairo::Cairo - Imported target find_path(CAIRO_INCLUDE_DIRS NAMES cairo/cairo.h PATHS ${CAIRO_ROOT_DIR} PATH_SUFFIXES include ) find_library(CAIRO_LIBRARY NAMES cairo PATHS ${CAIRO_ROOT_DIR} PATH_SUFFIXES lib lib64 ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Cairo REQUIRED_VARS CAIRO_LIBRARY CAIRO_INCLUDE_DIRS ) if(Cairo_FOUND AND NOT TARGET Cairo::Cairo) add_library(Cairo::Cairo UNKNOWN IMPORTED) set_target_properties(Cairo::Cairo PROPERTIES IMPORTED_LOCATION "${CAIRO_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${CAIRO_INCLUDE_DIRS}" ) endif() mark_as_advanced(CAIRO_INCLUDE_DIRS CAIRO_LIBRARY) ================================================ FILE: examples/GLES3-GLFW-video-demo/.gitignore ================================================ /build/ /website-demo-macos-glfw* ================================================ FILE: examples/GLES3-GLFW-video-demo/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(GLES3_GLFW_video_demo C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) # ------------------------------------------------- # FetchContent # ------------------------------------------------- include(FetchContent) set(FETCHCONTENT_QUIET FALSE) # ------------------------------------------------- # STB (header-only) # ------------------------------------------------- FetchContent_Declare( stb GIT_REPOSITORY https://github.com/nothings/stb.git GIT_TAG master ) FetchContent_MakeAvailable(stb) # ------------------------------------------------- # GLFW # ------------------------------------------------- FetchContent_Declare( glfw GIT_REPOSITORY https://github.com/glfw/glfw.git GIT_TAG 3.4 GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(glfw) # Disable examples/tests/docs (important) set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) # ------------------------------------------------- # Executable # ------------------------------------------------- add_executable(GLES3_GLFW_video_demo main.c ) target_include_directories(GLES3_GLFW_video_demo PUBLIC . ../.. ${stb_SOURCE_DIR} ) # ------------------------------------------------- # Link libraries # ------------------------------------------------- target_link_libraries(GLES3_GLFW_video_demo PRIVATE glfw ) # ------------------------------------------------- # Platform-specific OpenGL / GLES # ------------------------------------------------- if(APPLE) find_library(OPENGL_FRAMEWORK OpenGL) target_link_libraries(GLES3_GLFW_video_demo PRIVATE ${OPENGL_FRAMEWORK}) # Needed for GLFW on macOS target_link_libraries(GLES3_GLFW_video_demo PRIVATE "-framework Cocoa" "-framework IOKit" "-framework CoreVideo" ) elseif(WIN32) target_link_libraries(GLES3_GLFW_video_demo PRIVATE opengl32) elseif(UNIX) target_link_libraries(GLES3_GLFW_video_demo PRIVATE GL) endif() # ------------------------------------------------- # Build flags (kept minimal) # ------------------------------------------------- if(MSVC) target_compile_options(GLES3_GLFW_video_demo PRIVATE /W3) else() target_compile_options(GLES3_GLFW_video_demo PRIVATE -Wall -Wextra) endif() # ------------------------------------------------- # Copy resources # ------------------------------------------------- add_custom_command( TARGET GLES3_GLFW_video_demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources ) ================================================ FILE: examples/GLES3-GLFW-video-demo/Makefile.emscripten ================================================ # vim: set tabstop=4 shiftwidth=4 expandtab noexpandtab: # # PROGRAM COMPONENTS # CXX = emcc CXXFLAGS = -std=c99 CXXFLAGS += -O0 CXXFLAGS += -I../.. CXXFLAGS += -I./build/_deps/stb-src LDLIBS += -s USE_ZLIB=1 LDLIBS += -s USE_GLFW=3 LDLIBS += -s FULL_ES2=1 -s USE_WEBGL2=1 LDLIBS += -s ALLOW_MEMORY_GROWTH=1 -s GL_UNSAFE_OPTS=0 LDLIBS += -s STACK_SIZE=2048kb LDLIBS += -s EXPORTED_FUNCTIONS=['_main'] LDLIBS += -s ASSERTIONS=1 -s SAFE_HEAP=1 LDLIBS += --preload-file $(PWD)/resources/Roboto-Regular.ttf@resources/Roboto-Regular.ttf main: mkdir -p build/emscripten time $(CXX) $(CXXFLAGS) \ $(PWD)/main.c \ $(LDLIBS) -o build/emscripten/index.html test: make -f Makefile.emscripten main \ && (cd build/emscripten && python3 -mhttp.server) .PHONY: main ================================================ FILE: examples/GLES3-GLFW-video-demo/Makefile.macos ================================================ # vim: set tabstop=4 shiftwidth=4 expandtab noexpandtab: CXX = clang CXXFLAGS = -std=c99 CXXFLAGS += -g -O0 -fno-omit-frame-pointer CXXFLAGS += -ferror-limit=1 CXXFLAGS += -I../.. CXXFLAGS += -I./build/_deps/stb-src CXXFLAGS += -DGL_SILENCE_DEPRECATION # GLFW (Homebrew) CXXFLAGS += -I$(shell brew --prefix glfw)/include LDLIBS += -L$(shell brew --prefix glfw)/lib -lglfw # macOS system frameworks (OpenGL needs these) LDLIBS += -framework OpenGL LDLIBS += -framework Cocoa LDLIBS += -framework IOKit LDLIBS += -framework CoreVideo main: mkdir -p build time $(CXX) $(CXXFLAGS) \ $(PWD)/main.c \ $(LDLIBS) \ -o build/website-demo-macos-glfw ================================================ FILE: examples/GLES3-GLFW-video-demo/README.md ================================================ GLES3 Renderer Video Demo (Using GLFW) ====================================== This directory contains a standard Video-Demo example using work-in-progress GLES3 renderer. While it still needs refinement, the renderer is already functional and demonstrates the core rendering pipeline. Current features - Supports all draw commands except custom. - In the best-case scenario (no clipping): - All quad-based commands (Rectangle, Image, Border) are rendered in a single draw call. - All glyphs belonging to the same font are rendered in one instanced draw call. - When clipping (scissoring) is used: - The renderer flushes draw calls before and after each scissor region. - Supports up to 4 fonts and 4 image textures. - Image textures may also be used as texture atlases. - Custom UserData provides per-image UV coordinates, allowing multiple images to share a single OpenGL texture. - Uses stb_image.h and stb_truetype.h as single-header dependencies for asset loading. - The loading layer is modular and can be replaced with a different asset pipeline if needed. Currently builds on: - Emscripten - clang++ / macOS - CMake support is not available yet. Windowing and platform support This example uses GLFW, and the renderer is framework agnostic How to build it with CMake? --------------------------- Cmake build is the easiest way to build it: mkdir build cmake -S . -B ./build How to build and run on Emscripten: ---------------------------------- For Emscripten the build is a bit custom, but it depends on CMakeBuild to install header-stb dependency. So you still need to build it with CMake first. And then you have to source the Emscripten SDK: source /path/to/emscripten/emsdk/emsdk_env.sh Then build it with hand-crafted Makefile.emscripten: make -f Makefile.emscripten test and then navigate to http://localhost:8080 ================================================ FILE: examples/GLES3-GLFW-video-demo/main.c ================================================ #include #include #define STB_IMAGE_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION #define CLAY_IMPLEMENTATION #define CLAY_RENDERER_GLES3_IMPLEMENTATION #include #include "../../renderers/GLES3/clay_renderer_gles3.h" #include "../shared-layouts/clay-video-demo.c" #include "../../renderers/GLES3/clay_renderer_gles3_loader_stb.c" typedef struct VideoCtx { int shouldContinue; GLFWwindow *glfwWindow; int screenWidth, screenHeight; } VideoCtx; VideoCtx g_ctx; static int initVideo(VideoCtx *ctx, const int initialWidth, const int initialHeight) { if (!glfwInit()) { fprintf(stderr, "Failed to init GLFW\n"); return 0; } glfwDefaultWindowHints(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_SAMPLES, 4); // enable multisampling // NO solution for high DPI yet glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); g_ctx.glfwWindow = glfwCreateWindow(initialWidth, initialHeight, "GLES3 GLFW Video Demo", NULL, NULL); if (g_ctx.glfwWindow == NULL) { fprintf(stderr, "Failed to create GLFW window\n"); glfwTerminate(); return 0; } glfwMakeContextCurrent(g_ctx.glfwWindow); glfwGetWindowSize(g_ctx.glfwWindow, &g_ctx.screenWidth, &g_ctx.screenHeight); glViewport(0, 0, g_ctx.screenWidth, g_ctx.screenHeight); printf("Frame buffer size %dx%d\n", g_ctx.screenWidth, g_ctx.screenHeight); glEnable(GL_BLEND); // Enables blending, which allows transparent textures to be rendered properly. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Sets the blending function. // - `GL_SRC_ALPHA`: Uses the alpha value of the source (texture or color). // - `GL_ONE_MINUS_SRC_ALPHA`: Makes the destination color blend with the background based on alpha. // This is commonly used for standard transparency effects. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); // Enables depth testing, ensuring that objects closer to the camera are drawn in front of those farther away. // This prevents objects from rendering incorrectly based on draw order. return 1; } void My_ErrorHandler(Clay_ErrorData errorData) { printf("[ClaY ErroR] %s", errorData.errorText.chars); } Stb_FontData g_stbFonts[MAX_FONTS]; // Fonts userData Gles3_Renderer g_gles3; // The renderer itself static Clay_Vector2 g_scrollDelta = {0.0f, 0.0f}; static double g_lastTime = 0.0; static double g_deltaTime = 0.0; static void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { g_scrollDelta.x += (float)xoffset; g_scrollDelta.y += (float)yoffset; } void init() { size_t clayRequiredMemory = Clay_MinMemorySize(); g_gles3.clayMemory = (Clay_Arena){ .capacity = clayRequiredMemory, .memory = (char *)malloc(clayRequiredMemory), }; Clay_Context *clayCtx = Clay_Initialize( g_gles3.clayMemory, (Clay_Dimensions){ .width = (float)g_ctx.screenWidth, .height = (float)g_ctx.screenHeight, }, (Clay_ErrorHandler){ .errorHandlerFunction = My_ErrorHandler, }); // Note that MeasureText has to be set after the Context is set! Clay_SetCurrentContext(clayCtx); Clay_SetMeasureTextFunction(Stb_MeasureText, &g_stbFonts); // This example uses stb loader, but you can inject your custom loader // to load Images and Fonts if you don't want to use STB library Gles3_SetRenderTextFunction(&g_gles3, Stb_RenderText, &g_stbFonts); Gles3_Initialize(&g_gles3, 4096); int atlasW = 1024; int atlasH = 1024; if (!Stb_LoadFont( &g_gles3.fontTextures[0], &g_stbFonts[0], "resources/Roboto-Regular.ttf", 24.0f, // bake pixel height atlasW, atlasH)) abort(); Clay_SetDebugModeEnabled(true); glfwSetScrollCallback(g_ctx.glfwWindow, scroll_callback); } void loop() { Clay_Vector2 scrollDelta = {0.0f, 0.0f}; glfwPollEvents(); /* Quit handling */ if (glfwWindowShouldClose(g_ctx.glfwWindow)) { g_ctx.shouldContinue = false; } /* Consume scroll delta (accumulated via callback) */ scrollDelta = g_scrollDelta; g_scrollDelta.x = 0.0f; g_scrollDelta.y = 0.0f; /* Delta time (milliseconds, like your SDL version) */ double now = glfwGetTime(); // seconds g_deltaTime = (now - g_lastTime) * 1000.0; g_lastTime = now; double mouseX = 0.0; double mouseY = 0.0; glfwGetCursorPos(g_ctx.glfwWindow, &mouseX, &mouseY); Clay_Vector2 mousePosition = { (float)mouseX, (float)mouseY }; int mousePressed = glfwGetMouseButton(g_ctx.glfwWindow, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; Clay_SetPointerState(mousePosition, mousePressed); Clay_UpdateScrollContainers( true, (Clay_Vector2){scrollDelta.x, scrollDelta.y}, g_deltaTime); glfwGetWindowSize(g_ctx.glfwWindow, &g_ctx.screenWidth, &g_ctx.screenHeight); glViewport(0, 0, g_ctx.screenWidth, g_ctx.screenHeight); Clay_SetLayoutDimensions((Clay_Dimensions){(float)g_ctx.screenWidth, (float)g_ctx.screenHeight}); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); // Clay renderer is simple and never writes to depth buffer glClearColor(0.1f, 0.2f, 0.1f, 1.0f); ClayVideoDemo_Data data = ClayVideoDemo_Initialize(); Clay_RenderCommandArray cmds = ClayVideoDemo_CreateLayout(&data); Gles3_Render(&g_gles3, cmds, g_stbFonts); glfwSwapBuffers(g_ctx.glfwWindow); } // Just initializes and spins the animation loop int main() { initVideo(&g_ctx, 1280, 720); init(); g_ctx.shouldContinue = true; #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(loop, 0, 1); #else while (g_ctx.shouldContinue) { loop(); } #endif } ================================================ FILE: examples/GLES3-SDL2-sidebar-scrolling-container/.gitignore ================================================ /build/ /website-demo-macos-sdl2* /macos-sidebar-scrolling-container* ================================================ FILE: examples/GLES3-SDL2-sidebar-scrolling-container/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) set(CMAKE_C_STANDARD 99) project(GLES3_SDL2_sidebar_scrolling_container C) # ------------------------------------------------- # FetchContent # ------------------------------------------------- include(FetchContent) set(FETCHCONTENT_QUIET FALSE) # ------------------------------------------------- # STB (header-only) # ------------------------------------------------- FetchContent_Declare( stb GIT_REPOSITORY https://github.com/nothings/stb.git GIT_TAG master ) FetchContent_MakeAvailable(stb) # ------------------------------------------------- # SDL2 # ------------------------------------------------- FetchContent_Declare( SDL2 GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" GIT_TAG "release-2.30.10" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(SDL2) # ------------------------------------------------- # Executable # ------------------------------------------------- add_executable(GLES3_SDL2_sidebar_scrolling_container main.c) target_compile_options(GLES3_SDL2_sidebar_scrolling_container PUBLIC) target_include_directories(GLES3_SDL2_sidebar_scrolling_container PUBLIC . # This renderer ../.. # Clay ${stb_SOURCE_DIR} # STB header only depencency that does not have its own CMake build ) # ------------------------------------------------- # Link libraries # ------------------------------------------------- target_link_libraries(GLES3_SDL2_sidebar_scrolling_container PUBLIC SDL2::SDL2main SDL2::SDL2-static ) # ------------------------------------------------- # Platform-specific OpenGL / GLES # ------------------------------------------------- find_package(SDL2 REQUIRED) find_library(OPENGL_FRAMEWORK OpenGL) target_link_libraries(GLES3_SDL2_sidebar_scrolling_container PRIVATE ${OPENGL_FRAMEWORK} ) # ------------------------------------------------- # Build flags (kept minimal) # ------------------------------------------------- if(MSVC) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") else() set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") endif() # ------------------------------------------------- # Copy resources # ------------------------------------------------- add_custom_command( TARGET GLES3_SDL2_sidebar_scrolling_container POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources ) ================================================ FILE: examples/GLES3-SDL2-sidebar-scrolling-container/Makefile.emscripten ================================================ # vim: set tabstop=4 shiftwidth=4 expandtab noexpandtab: # # PROGRAM COMPONENTS # CXX = emcc CXXFLAGS = -std=c99 CXXFLAGS += -O0 CXXFLAGS += -I../.. CXXFLAGS += -I./build/_deps/stb-src LDLIBS += -s USE_ZLIB=1 LDLIBS += -s USE_SDL=2 LDLIBS += -s FULL_ES2=1 -s USE_WEBGL2=1 LDLIBS += -s ALLOW_MEMORY_GROWTH=1 -s GL_UNSAFE_OPTS=0 LDLIBS += -s STACK_SIZE=2048kb LDLIBS += -s EXPORTED_FUNCTIONS=['_main'] LDLIBS += -s ASSERTIONS=1 -s SAFE_HEAP=1 LDLIBS += --preload-file $(PWD)/resources/profile-picture.png@resources/profile-picture.png LDLIBS += --preload-file $(PWD)/resources/millbank.jpeg@resources/millbank.jpeg LDLIBS += --preload-file $(PWD)/resources/Roboto-Regular.ttf@resources/Roboto-Regular.ttf LDLIBS += --preload-file $(PWD)/resources/RobotoMono-Medium.ttf@resources/RobotoMono-Medium.ttf main: mkdir -p build/emscripten time $(CXX) $(CXXFLAGS) \ $(PWD)/main.c \ $(LDLIBS) -o build/emscripten/index.html test: make -f Makefile.emscripten main \ && (cd build/emscripten && python3 -mhttp.server) .PHONY: main ================================================ FILE: examples/GLES3-SDL2-sidebar-scrolling-container/Makefile.macos ================================================ # vim: set tabstop=4 shiftwidth=4 expandtab noexpandtab: CXX = clang CXXFLAGS = -std=c99 CXXFLAGS += -g -O0 -fno-omit-frame-pointer CXXFLAGS += -ferror-limit=1 CXXFLAGS += -I../.. CXXFLAGS += -I./build/_deps/stb-src CXXFLAGS += -DGL_SILENCE_DEPRECATION # SDL2 (Homebrew) CXXFLAGS += -I$(shell brew --prefix sdl2)/include/SDL2 LDLIBS += -L$(shell brew --prefix sdl2)/lib -lSDL2 # macOS system frameworks (OpenGL needs these) LDLIBS += -framework OpenGL LDLIBS += -framework Cocoa LDLIBS += -framework IOKit LDLIBS += -framework CoreVideo main: mkdir -p build time $(CXX) $(CXXFLAGS) \ $(PWD)/main.c \ $(LDLIBS) \ -o build/macos-sidebar-scrolling-container-sdl2 ================================================ FILE: examples/GLES3-SDL2-sidebar-scrolling-container/README.md ================================================ GLES3 Renderer Scrolling Container (Using SDL2) =============================================== This directory contains a complete example thatn can me used to test all different draw commands using work-in-progress GLES3 renderer. While it still needs refinement, the renderer is already functional and demonstrates the core rendering pipeline. The images used as resources in this example, namely: - millbank.jpeg a window with a panoramic city view; - and profile-picture.png showing objects aligned in a circle; are taken with my phone camera and are dedicated to the Public Domain. How to build it with CMake? --------------------------- Cmake build is the easiest way to build it: mkdir build cmake -S . -B ./build How to build and run on Emscripten: ---------------------------------- For Emscripten the build is a bit custom, but it depends on CMakeBuild to install header-stb dependency. So you still need to build it with CMake first. And then you have to source the Emscripten SDK: source /path/to/emscripten/emsdk/emsdk_env.sh Then build it with hand-crafted Makefile.emscripten: make -f Makefile.emscripten test and then navigate to http://localhost:8080 How to build and run on Mac: ---------------------------- Requires SDL2: brew install sdl2 Requires STB: cmake -B build Build it with: make -f Makefile.macos And run with: ./macos-sidebar-scrolling-container-sdl2 ================================================ FILE: examples/GLES3-SDL2-sidebar-scrolling-container/main.c ================================================ #include #include #define STB_IMAGE_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION #define CLAY_IMPLEMENTATION #define CLAY_RENDERER_GLES3_IMPLEMENTATION #include #include "../../renderers/GLES3/clay_renderer_gles3.h" #include "../shared-layouts/clay-video-demo.c" #include "../../renderers/GLES3/clay_renderer_gles3_loader_stb.c" typedef struct VideoCtx { int shouldContinue; SDL_Window *sdlWindow; SDL_GLContext sdlContext; int screenWidth, screenHeight; } VideoCtx; VideoCtx g_ctx; static int initVideo(VideoCtx *ctx, const int initialWidth, const int initialHeight) { SDL_Init(SDL_INIT_VIDEO); #if defined(__EMSCRIPTEN__) // OpenGL ES 3 profile SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #else // Apple MacOs will use it own legacy desktop GL instead // I know, I lied, I said this was an GLES3 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); #endif SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); g_ctx.sdlWindow = SDL_CreateWindow( "SDL2 GLES3", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, initialWidth, initialHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN); g_ctx.sdlContext = SDL_GL_CreateContext(g_ctx.sdlWindow); SDL_ShowWindow(g_ctx.sdlWindow); SDL_Delay(1); SDL_GL_GetDrawableSize(g_ctx.sdlWindow, &g_ctx.screenWidth, &g_ctx.screenHeight); glViewport(0, 0, g_ctx.screenWidth, g_ctx.screenHeight); glEnable(GL_BLEND); // Enables blending, which allows transparent textures to be rendered properly. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Sets the blending function. // - `GL_SRC_ALPHA`: Uses the alpha value of the source (texture or color). // - `GL_ONE_MINUS_SRC_ALPHA`: Makes the destination color blend with the background based on alpha. // This is commonly used for standard transparency effects. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); // Enables depth testing, ensuring that objects closer to the camera are drawn in front of those farther away. // This prevents objects from rendering incorrectly based on draw order. return 1; } void My_ErrorHandler(Clay_ErrorData errorData) { printf("[ClaY ErroR] %s", errorData.errorText.chars); } Stb_FontData g_stbFonts[MAX_FONTS]; // Fonts userData Gles3_Renderer g_gles3; // The renderer itself Uint64 NOW = 0; Uint64 LAST = 0; double deltaTime = 0; uint64_t g_drawCallsDuringLastFrame = 0; double g_timeAccumulator = 0.0; double g_avgFrameMs = 0.0; double g_fps = 0.0; int g_frameCount = 0; char g_fpsText[128]; size_t g_fpsTextLen = 0; static double g_wallTimeAccumulator = 0.0; char g_glInfoText[512]; size_t g_glInfoTextLen; void init() { size_t clayRequiredMemory = Clay_MinMemorySize(); g_gles3.clayMemory = (Clay_Arena){ .capacity = clayRequiredMemory, .memory = (char *)malloc(clayRequiredMemory), }; Clay_Context *clayCtx = Clay_Initialize( g_gles3.clayMemory, (Clay_Dimensions){ .width = (float)g_ctx.screenWidth, .height = (float)g_ctx.screenHeight, }, (Clay_ErrorHandler){ .errorHandlerFunction = My_ErrorHandler, }); // Note that MeasureText has to be set after the Context is set! Clay_SetCurrentContext(clayCtx); Clay_SetMeasureTextFunction(Stb_MeasureText, &g_stbFonts); Gles3_SetRenderTextFunction(&g_gles3, Stb_RenderText, &g_stbFonts); Gles3_Initialize(&g_gles3, 4096); int atlasW = 1024; int atlasH = 1024; if (!Stb_LoadFont( &g_gles3.fontTextures[0], &g_stbFonts[0], "resources/Roboto-Regular.ttf", 24.0f, // bake pixel height atlasW, atlasH)) abort(); if (!Stb_LoadFont( &g_gles3.fontTextures[1], &g_stbFonts[1], "resources/RobotoMono-Medium.ttf", 24.0f, // bake pixel height atlasW, atlasH)) abort(); if (!Stb_LoadImage( &g_gles3.imageTextures[0], "resources/profile-picture.png")) abort(); if (!Stb_LoadImage( &g_gles3.imageTextures[1], "resources/millbank.jpeg")) abort(); Clay_SetDebugModeEnabled(true); const GLubyte *glVersion = glGetString(GL_VERSION); const GLubyte *glslVersion = glGetString(GL_SHADING_LANGUAGE_VERSION); const GLubyte *vendor = glGetString(GL_VENDOR); const GLubyte *renderer = glGetString(GL_RENDERER); g_glInfoTextLen = (size_t)snprintf( g_glInfoText, sizeof(g_glInfoText), "OpenGL Version : %s\n" "GLSL Version : %s\n" "Vendor : %s\n" "Renderer : %s", glVersion ? (const char *)glVersion : "unknown", glslVersion ? (const char *)glslVersion : "unknown", vendor ? (const char *)vendor : "unknown", renderer ? (const char *)renderer : "unknown"); } Gles3_ImageConfig g_profilePicture = (Gles3_ImageConfig){ .textureToUse = 0, .u0 = 0.0f, .v0 = 0.0f, .u1 = 1.0f, .v1 = 1.0f, }; Gles3_ImageConfig g_window1 = (Gles3_ImageConfig){ .textureToUse = 1, .u0 = 0.0f, .v0 = 0.35f, .u1 = 0.18f, .v1 = 0.75f, }; Gles3_ImageConfig g_window2 = (Gles3_ImageConfig){ .textureToUse = 1, .u0 = 0.25f, .v0 = 0.35f, .u1 = 0.47f, .v1 = 0.75f, }; Gles3_ImageConfig g_window3 = (Gles3_ImageConfig){ .textureToUse = 1, .u0 = 0.52f, .v0 = 0.35f, .u1 = 0.76f, .v1 = 0.75f, }; Gles3_ImageConfig g_window4 = (Gles3_ImageConfig){ .textureToUse = 1, .u0 = 0.82f, .v0 = 0.35f, .u1 = 1.0f, .v1 = 0.75f, }; Clay_LayoutConfig dropdownTextItemLayout = {.padding = {8, 8, 4, 4}}; Clay_TextElementConfig dropdownTextElementConfig = {.fontSize = 24, .textColor = {55, 55, 55, 255}}; void RenderDropdownTextItem(int index) { CLAY_AUTO_ID({.layout = dropdownTextItemLayout, .backgroundColor = {220, 220, 220, 255}}) { CLAY_TEXT(CLAY_STRING("I'm a text field in a scroll container."), &dropdownTextElementConfig); } } const uint32_t FONT_ID_BODY_24 = 1; Clay_String profileText = CLAY_STRING_CONST("Profile Page one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen"); void RenderHeaderButton1(Clay_String text) { CLAY_AUTO_ID( {.layout = { .padding = {16, 16, 8, 8}}, .backgroundColor = {140, 140, 140, 255}, .border = {.width = CLAY_BORDER_OUTSIDE(14), .color = {180, 80, 80, 255}}, .cornerRadius = CLAY_CORNER_RADIUS(5)}) { CLAY_TEXT(text, CLAY_TEXT_CONFIG({.fontId = FONT_ID_BODY_16, .fontSize = 16, .textColor = {255, 255, 255, 255}})); } } void RenderHeaderButton2(Clay_String text) { CLAY_AUTO_ID( {.layout = { .padding = {16, 16, 8, 8}}, .backgroundColor = {140, 140, 140, 255}, // .border = { .width = CLAY_BORDER_OUTSIDE(4), .color = {180, 80, 80, 255} }, .cornerRadius = CLAY_CORNER_RADIUS(5)}) { CLAY_TEXT(text, CLAY_TEXT_CONFIG({.fontId = FONT_ID_BODY_16, .fontSize = 16, .textColor = {255, 255, 255, 255}})); } } void RenderHeaderButton3(Clay_String text) { CLAY_AUTO_ID( {.layout = { .padding = {16, 16, 8, 8}}, .backgroundColor = {140, 140, 140, 255}, .border = {.width = CLAY_BORDER_OUTSIDE(14), .color = {180, 80, 80, 255}}, .cornerRadius = CLAY_CORNER_RADIUS(0)}) { CLAY_TEXT(text, CLAY_TEXT_CONFIG({.fontId = FONT_ID_BODY_16, .fontSize = 16, .textColor = {255, 255, 255, 255}})); } } void RenderHeaderButton4(Clay_String text) { CLAY_AUTO_ID( {.layout = { .padding = {16, 16, 8, 8}}, .backgroundColor = {140, 140, 140, 255}, .border = {.width = CLAY_BORDER_OUTSIDE(4), .color = {180, 80, 80, 255}}, .cornerRadius = CLAY_CORNER_RADIUS(5)}) { CLAY_TEXT(text, CLAY_TEXT_CONFIG({.fontId = FONT_ID_BODY_16, .fontSize = 16, .textColor = {255, 255, 255, 255}})); } } Clay_RenderCommandArray CreateLayout(void) { Clay_BeginLayout(); CLAY(CLAY_ID("OuterContainer"), {.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, .padding = {16, 16, 16, 16}, .childGap = 16}, .backgroundColor = {200, 200, 200, 255}}) { CLAY(CLAY_ID("SideBar"), {.layout = {.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = {.width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0)}, .padding = {16, 16, 16, 16}, .childGap = 16}, .backgroundColor = {150, 150, 255, 255}}) { CLAY(CLAY_ID("ProfilePictureOuter"), {.layout = {.sizing = {.width = CLAY_SIZING_GROW(0)}, .padding = {8, 8, 8, 8}, .childGap = 8, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}}, .backgroundColor = {130, 130, 255, 255}}) { CLAY(CLAY_ID("ProfilePicture"), { .layout = {.sizing = {.width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60)}}, .image = {.imageData = &g_profilePicture}, .cornerRadius = {30, 30, 30, 30}, }) { } CLAY_TEXT(profileText, CLAY_TEXT_CONFIG({.fontSize = 24, .textColor = {0, 0, 0, 255}, .textAlignment = CLAY_TEXT_ALIGN_RIGHT})); } CLAY(CLAY_ID("SidebarBlob1"), {.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50)}}, .backgroundColor = {110, 110, 255, 255}}) {} CLAY(CLAY_ID("SidebarBlob2"), {.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50)}}, .backgroundColor = {110, 110, 255, 255}}) {} CLAY(CLAY_ID("SidebarBlob3"), {.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50)}}, .backgroundColor = {110, 110, 255, 255}}) {} CLAY(CLAY_ID("SidebarBlob4"), {.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50)}}, .backgroundColor = {110, 110, 255, 255}}) {} } CLAY(CLAY_ID("RightPanel"), {.layout = {.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, .childGap = 16}}) { CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0)}, .childAlignment = {.x = CLAY_ALIGN_X_RIGHT}, .padding = {8, 8, 8, 8}, .childGap = 18}, .backgroundColor = {180, 180, 180, 255}}) { RenderHeaderButton1(CLAY_STRING("Header Item 1")); RenderHeaderButton2(CLAY_STRING("Header Item 2")); RenderHeaderButton3(CLAY_STRING("Header Item 3")); RenderHeaderButton4(CLAY_STRING("Header Item 4")); } CLAY( CLAY_ID("MainContent"), { .layout = {.layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {16, 16, 16, 16}, .childGap = 16, .sizing = {.width = CLAY_SIZING_GROW(0)}}, .backgroundColor = {200, 200, 255, 255}, .clip = {.vertical = true, .childOffset = Clay_GetScrollOffset()}, }) { CLAY( CLAY_ID("FloatingContainer"), { .layout = {.sizing = {.width = CLAY_SIZING_PERCENT(0.5), .height = CLAY_SIZING_FIXED(300)}, .padding = {16, 16, 16, 16}}, .backgroundColor = {140, 80, 200, 200}, .floating = {.attachTo = CLAY_ATTACH_TO_PARENT, .zIndex = 1, .attachPoints = {CLAY_ATTACH_POINT_CENTER_TOP, CLAY_ATTACH_POINT_CENTER_TOP}, .offset = {0, 0}}, .border = {.width = CLAY_BORDER_OUTSIDE(4), .color = {80, 80, 80, 255}}, .cornerRadius = {30, 3, 3, 30}, }) { CLAY_TEXT( CLAY_STRING("I'm an inline floating container."), CLAY_TEXT_CONFIG({.fontSize = 24, .textColor = {255, 255, 255, 255}})); } CLAY_TEXT( CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt."), CLAY_TEXT_CONFIG({.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {0, 0, 0, 255}})); CLAY_TEXT( CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt."), CLAY_TEXT_CONFIG({.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {0, 0, 0, 255}})); CLAY_TEXT(CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt."), CLAY_TEXT_CONFIG({.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {0, 0, 0, 255}})); CLAY(CLAY_ID("Photos2"), {.layout = {.childGap = 16, .padding = {16, 16, 16, 16}}, .backgroundColor = {180, 180, 220, (float)(Clay_Hovered() ? 120 : 255)}}) { CLAY(CLAY_ID("Picture4"), {.layout = {.sizing = {.width = CLAY_SIZING_FIXED(100), .height = CLAY_SIZING_FIXED(120)}}, .image = {.imageData = &g_window1}}) {} CLAY(CLAY_ID("Picture5"), {.layout = {.sizing = {.width = CLAY_SIZING_FIXED(100), .height = CLAY_SIZING_FIXED(120)}}, .image = {.imageData = &g_window2}}) {} CLAY(CLAY_ID("Picture6"), {.layout = {.sizing = {.width = CLAY_SIZING_FIXED(100), .height = CLAY_SIZING_FIXED(120)}}, .image = {.imageData = &g_window3}}) {} CLAY(CLAY_ID("Picture6.5"), {.layout = {.sizing = {.width = CLAY_SIZING_FIXED(100), .height = CLAY_SIZING_FIXED(120)}}, .image = {.imageData = &g_window4}}) {} } Clay_String cs = {.isStaticallyAllocated = false, .length = g_glInfoTextLen, .chars = g_glInfoText}; Clay_TextElementConfig glInfoElementConfig = {.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {255, 255, 255, 255}}; CLAY_TEXT(cs, &glInfoElementConfig); CLAY_TEXT( CLAY_STRING("Faucibus purus in massa tempor nec. Nec ullamcorper sit amet risus nullam eget felis eget nunc. Diam vulputate ut pharetra sit amet aliquam id diam. Lacus suspendisse faucibus interdum posuere lorem. A diam sollicitudin tempor id. Amet massa vitae tortor condimentum lacinia. Aliquet nibh praesent tristique magna."), CLAY_TEXT_CONFIG({.fontSize = 24, .lineHeight = 60, .textColor = {0, 0, 0, 255}, .textAlignment = CLAY_TEXT_ALIGN_CENTER})); CLAY_TEXT(CLAY_STRING("Suspendisse in est ante in nibh. Amet venenatis urna cursus eget nunc scelerisque viverra. Elementum sagittis vitae et leo duis ut diam quam nulla. Enim nulla aliquet porttitor lacus. Pellentesque habitant morbi tristique senectus et. Facilisi nullam vehicula ipsum a arcu cursus vitae.\nSem fringilla ut morbi tincidunt. Euismod quis viverra nibh cras pulvinar mattis nunc sed. Velit sed ullamcorper morbi tincidunt ornare massa. Varius quam quisque id diam vel quam. Nulla pellentesque dignissim enim sit amet venenatis. Enim lobortis scelerisque fermentum dui faucibus in. Pretium viverra suspendisse potenti nullam ac tortor vitae. Lectus vestibulum mattis ullamcorper velit sed. Eget mauris pharetra et ultrices neque ornare aenean euismod elementum. Habitant morbi tristique senectus et. Integer vitae justo eget magna fermentum iaculis eu. Semper quis lectus nulla at volutpat diam. Enim praesent elementum facilisis leo. Massa vitae tortor condimentum lacinia quis vel."), CLAY_TEXT_CONFIG({.fontSize = 24, .textColor = {0, 0, 0, 255}})); CLAY(CLAY_ID("Photos"), {.layout = {.sizing = {.width = CLAY_SIZING_GROW(0)}, .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .childGap = 16, .padding = {16, 16, 16, 16}}, .backgroundColor = {180, 180, 220, 255}}) { CLAY(CLAY_ID("Picture2"), {.layout = {.sizing = {.width = CLAY_SIZING_FIXED(120)}}, .aspectRatio = 1, .image = {.imageData = &g_profilePicture}}) {} CLAY(CLAY_ID("Picture1"), {.layout = {.childAlignment = {.x = CLAY_ALIGN_X_CENTER}, .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {8, 8, 8, 8}}, .backgroundColor = {170, 170, 220, 255}}) { CLAY(CLAY_ID("ProfilePicture2"), {.layout = {.sizing = {.width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60)}}, .image = {.imageData = &g_profilePicture}}) {} CLAY_TEXT(CLAY_STRING("Image caption below"), CLAY_TEXT_CONFIG({.fontSize = 24, .textColor = {0, 0, 0, 255}})); } CLAY(CLAY_ID("Picture3"), {.layout = {.sizing = {.width = CLAY_SIZING_FIXED(120)}}, .aspectRatio = 1, .image = {.imageData = &g_profilePicture}}) {} } CLAY_TEXT( CLAY_STRING("Amet cursus sit amet dictum sit amet justo donec. Et malesuada fames ac turpis egestas maecenas. A lacus vestibulum sed arcu non odio euismod lacinia. Gravida neque convallis a cras. Dui nunc mattis enim ut tellus elementum sagittis vitae et. Orci sagittis eu volutpat odio facilisis mauris. Neque gravida in fermentum et sollicitudin ac orci. Ultrices dui sapien eget mi proin sed libero. Euismod quis viverra nibh cras pulvinar mattis. Diam volutpat commodo sed egestas egestas. In fermentum posuere urna nec tincidunt praesent semper. Integer eget aliquet nibh praesent tristique magna.\nId cursus metus aliquam eleifend mi in. Sed pulvinar proin gravida hendrerit lectus a. Etiam tempor orci eu lobortis elementum nibh tellus. Nullam vehicula ipsum a arcu cursus vitae. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Condimentum lacinia quis vel eros donec ac odio. Mattis pellentesque id nibh tortor id aliquet lectus. Turpis egestas integer eget aliquet nibh praesent tristique. Porttitor massa id neque aliquam vestibulum morbi. Mauris commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Nunc scelerisque viverra mauris in aliquam sem fringilla. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla.\nLacus laoreet non curabitur gravida arcu ac tortor dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Tristique senectus et netus et malesuada fames ac. Nunc aliquet bibendum enim facilisis gravida. Egestas maecenas pharetra convallis posuere morbi leo urna molestie. Sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Ac turpis egestas maecenas pharetra convallis posuere morbi leo urna. Viverra vitae congue eu consequat. Aliquet enim tortor at auctor urna. Ornare massa eget egestas purus viverra accumsan in nisl nisi. Elit pellentesque habitant morbi tristique senectus et netus et malesuada.\nSuspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Lobortis feugiat vivamus at augue eget arcu. Vitae justo eget magna fermentum iaculis eu. Gravida rutrum quisque non tellus orci. Ipsum faucibus vitae aliquet nec. Nullam non nisi est sit amet. Nunc consequat interdum varius sit amet mattis vulputate enim. Sem fringilla ut morbi tincidunt augue interdum. Vitae purus faucibus ornare suspendisse. Massa tincidunt nunc pulvinar sapien et. Fringilla ut morbi tincidunt augue interdum velit euismod in. Donec massa sapien faucibus et. Est placerat in egestas erat imperdiet. Gravida rutrum quisque non tellus. Morbi non arcu risus quis varius quam quisque id diam. Habitant morbi tristique senectus et netus et malesuada fames ac. Eget lorem dolor sed viverra.\nOrnare massa eget egestas purus viverra. Varius vel pharetra vel turpis nunc eget lorem. Consectetur purus ut faucibus pulvinar elementum. Placerat in egestas erat imperdiet sed euismod nisi. Interdum velit euismod in pellentesque massa placerat duis ultricies lacus. Aliquam nulla facilisi cras fermentum odio eu. Est pellentesque elit ullamcorper dignissim cras tincidunt. Nunc sed id semper risus in hendrerit gravida rutrum. A pellentesque sit amet porttitor eget dolor morbi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Sed id semper risus in hendrerit gravida. Tincidunt praesent semper feugiat nibh. Aliquet lectus proin nibh nisl condimentum id venenatis a. Enim sit amet venenatis urna cursus eget. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Lacinia quis vel eros donec ac odio tempor orci. Donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Erat pellentesque adipiscing commodo elit at.\nEgestas sed sed risus pretium quam vulputate. Vitae congue mauris rhoncus aenean vel elit scelerisque mauris pellentesque. Aliquam malesuada bibendum arcu vitae elementum. Congue mauris rhoncus aenean vel elit scelerisque mauris. Pellentesque dignissim enim sit amet venenatis urna cursus. Et malesuada fames ac turpis egestas sed tempus urna. Vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Nibh cras pulvinar mattis nunc sed blandit libero. Fringilla est ullamcorper eget nulla facilisi etiam dignissim. Aenean euismod elementum nisi quis eleifend quam adipiscing vitae proin. Mauris pharetra et ultrices neque ornare aenean euismod elementum. Ornare quam viverra orci sagittis eu. Odio ut sem nulla pharetra diam sit amet nisl suscipit. Ornare lectus sit amet est. Ullamcorper sit amet risus nullam eget. Tincidunt lobortis feugiat vivamus at augue eget arcu dictum.\nUrna nec tincidunt praesent semper feugiat nibh. Ut venenatis tellus in metus vulputate eu scelerisque felis. Cursus risus at ultrices mi tempus. In pellentesque massa placerat duis ultricies lacus sed turpis. Platea dictumst quisque sagittis purus. Cras adipiscing enim eu turpis egestas. Egestas sed tempus urna et pharetra pharetra. Netus et malesuada fames ac turpis egestas integer eget aliquet. Ac turpis egestas sed tempus. Sed lectus vestibulum mattis ullamcorper velit sed. Ante metus dictum at tempor commodo ullamcorper a. Augue neque gravida in fermentum et sollicitudin ac. Praesent semper feugiat nibh sed pulvinar proin gravida. Metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices. Neque gravida in fermentum et sollicitudin ac orci phasellus egestas.\nRidiculus mus mauris vitae ultricies. Morbi quis commodo odio aenean. Duis ultricies lacus sed turpis. Non pulvinar neque laoreet suspendisse interdum consectetur. Scelerisque eleifend donec pretium vulputate sapien nec sagittis aliquam. Volutpat est velit egestas dui id ornare arcu odio ut. Viverra tellus in hac habitasse platea dictumst vestibulum rhoncus est. Vestibulum lectus mauris ultrices eros. Sed blandit libero volutpat sed cras ornare. Id leo in vitae turpis massa sed elementum tempus. Gravida dictum fusce ut placerat orci nulla pellentesque. Pretium quam vulputate dignissim suspendisse in. Nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Risus viverra adipiscing at in tellus. Turpis nunc eget lorem dolor sed viverra ipsum. Senectus et netus et malesuada fames ac. Habitasse platea dictumst vestibulum rhoncus est. Nunc sed id semper risus in hendrerit gravida. Felis eget velit aliquet sagittis id. Eget felis eget nunc lobortis.\nMaecenas pharetra convallis posuere morbi leo. Maecenas volutpat blandit aliquam etiam. A condimentum vitae sapien pellentesque habitant morbi tristique senectus et. Pulvinar mattis nunc sed blandit libero volutpat sed. Feugiat in ante metus dictum at tempor commodo ullamcorper. Vel pharetra vel turpis nunc eget lorem dolor. Est placerat in egestas erat imperdiet sed euismod. Quisque non tellus orci ac auctor augue mauris augue. Placerat vestibulum lectus mauris ultrices eros in cursus turpis. Enim nunc faucibus a pellentesque sit. Adipiscing vitae proin sagittis nisl. Iaculis at erat pellentesque adipiscing commodo elit at imperdiet. Aliquam sem fringilla ut morbi.\nArcu odio ut sem nulla pharetra diam sit amet nisl. Non diam phasellus vestibulum lorem sed. At erat pellentesque adipiscing commodo elit at. Lacus luctus accumsan tortor posuere ac ut consequat. Et malesuada fames ac turpis egestas integer. Tristique magna sit amet purus. A condimentum vitae sapien pellentesque habitant. Quis varius quam quisque id diam vel quam. Est ullamcorper eget nulla facilisi etiam dignissim diam quis. Augue interdum velit euismod in pellentesque massa. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant. Vulputate eu scelerisque felis imperdiet. Nibh tellus molestie nunc non blandit massa. Velit euismod in pellentesque massa placerat. Sed cras ornare arcu dui. Ut sem viverra aliquet eget sit. Eu lobortis elementum nibh tellus molestie nunc non. Blandit libero volutpat sed cras ornare arcu dui vivamus.\nSit amet aliquam id diam maecenas. Amet risus nullam eget felis eget nunc lobortis mattis aliquam. Magna sit amet purus gravida. Egestas purus viverra accumsan in nisl nisi. Leo duis ut diam quam. Ante metus dictum at tempor commodo ullamcorper. Ac turpis egestas integer eget. Fames ac turpis egestas integer eget aliquet nibh. Sem integer vitae justo eget magna fermentum. Semper auctor neque vitae tempus quam pellentesque nec nam aliquam. Vestibulum mattis ullamcorper velit sed. Consectetur adipiscing elit duis tristique sollicitudin nibh. Massa id neque aliquam vestibulum morbi blandit cursus risus.\nCursus sit amet dictum sit amet justo donec enim diam. Egestas erat imperdiet sed euismod. Nullam vehicula ipsum a arcu cursus vitae congue mauris. Habitasse platea dictumst vestibulum rhoncus est pellentesque elit. Duis ultricies lacus sed turpis tincidunt id aliquet risus feugiat. Faucibus ornare suspendisse sed nisi lacus sed viverra. Pretium fusce id velit ut tortor pretium viverra. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel. Senectus et netus et malesuada. Tellus pellentesque eu tincidunt tortor aliquam. Aenean sed adipiscing diam donec adipiscing tristique risus nec feugiat. Quis vel eros donec ac odio. Id interdum velit laoreet id donec ultrices tincidunt.\nMassa id neque aliquam vestibulum morbi blandit cursus risus at. Enim tortor at auctor urna nunc id cursus metus. Lorem ipsum dolor sit amet consectetur. At quis risus sed vulputate odio. Facilisis mauris sit amet massa vitae tortor condimentum lacinia quis. Et malesuada fames ac turpis egestas maecenas. Bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim. Viverra orci sagittis eu volutpat odio facilisis mauris. Adipiscing bibendum est ultricies integer quis auctor elit sed. Neque viverra justo nec ultrices dui sapien. Elementum nibh tellus molestie nunc non blandit massa enim. Euismod elementum nisi quis eleifend quam adipiscing vitae proin sagittis. Faucibus ornare suspendisse sed nisi. Quis viverra nibh cras pulvinar mattis nunc sed blandit. Tristique senectus et netus et. Magnis dis parturient montes nascetur ridiculus mus.\nDolor magna eget est lorem ipsum dolor. Nibh sit amet commodo nulla. Donec pretium vulputate sapien nec sagittis aliquam malesuada. Cras adipiscing enim eu turpis egestas pretium. Cras ornare arcu dui vivamus arcu felis bibendum ut tristique. Mus mauris vitae ultricies leo integer. In nulla posuere sollicitudin aliquam ultrices sagittis orci. Quis hendrerit dolor magna eget. Nisl tincidunt eget nullam non. Vitae congue eu consequat ac felis donec et odio. Vivamus at augue eget arcu dictum varius duis at. Ornare quam viverra orci sagittis.\nErat nam at lectus urna duis convallis. Massa placerat duis ultricies lacus sed turpis tincidunt id aliquet. Est ullamcorper eget nulla facilisi etiam dignissim diam. Arcu vitae elementum curabitur vitae nunc sed velit dignissim sodales. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus. Neque viverra justo nec ultrices dui sapien eget mi proin. Viverra accumsan in nisl nisi scelerisque eu ultrices. Consequat interdum varius sit amet mattis. In aliquam sem fringilla ut morbi. Eget arcu dictum varius duis at. Nulla aliquet porttitor lacus luctus accumsan tortor posuere. Arcu bibendum at varius vel pharetra vel turpis. Hac habitasse platea dictumst quisque sagittis purus sit amet. Sapien eget mi proin sed libero enim sed. Quam elementum pulvinar etiam non quam lacus suspendisse faucibus interdum. Semper viverra nam libero justo. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Et malesuada fames ac turpis egestas maecenas pharetra convallis posuere.\nTurpis egestas sed tempus urna et pharetra pharetra massa. Gravida in fermentum et sollicitudin ac orci phasellus. Ornare suspendisse sed nisi lacus sed viverra tellus in. Fames ac turpis egestas maecenas pharetra convallis posuere. Mi proin sed libero enim sed faucibus turpis. Sit amet mauris commodo quis imperdiet massa tincidunt nunc. Ut etiam sit amet nisl purus in mollis nunc. Habitasse platea dictumst quisque sagittis purus sit amet volutpat consequat. Eget aliquet nibh praesent tristique magna. Sit amet est placerat in egestas erat. Commodo sed egestas egestas fringilla. Enim nulla aliquet porttitor lacus luctus accumsan tortor posuere ac. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Dignissim convallis aenean et tortor at risus viverra. Morbi blandit cursus risus at ultrices mi. Ac turpis egestas integer eget aliquet nibh praesent tristique magna.\nVolutpat sed cras ornare arcu dui. Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam. Viverra justo nec ultrices dui sapien. Amet risus nullam eget felis eget nunc lobortis. Metus aliquam eleifend mi in. Ut eu sem integer vitae. Auctor elit sed vulputate mi sit amet. Nisl nisi scelerisque eu ultrices. Dictum fusce ut placerat orci nulla. Pellentesque habitant morbi tristique senectus et. Auctor elit sed vulputate mi sit. Tincidunt arcu non sodales neque. Mi in nulla posuere sollicitudin aliquam. Morbi non arcu risus quis varius quam quisque id diam. Cras adipiscing enim eu turpis egestas pretium aenean pharetra magna. At auctor urna nunc id cursus metus aliquam. Mauris a diam maecenas sed enim ut sem viverra. Nunc scelerisque viverra mauris in. In iaculis nunc sed augue lacus viverra vitae congue eu. Volutpat blandit aliquam etiam erat velit scelerisque in dictum non."), CLAY_TEXT_CONFIG({.fontSize = 24, .textColor = {0, 0, 0, 255}})); } CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0)}, .padding = {8, 8, 8, 8}, .childGap = 8}, .backgroundColor = {180, 180, 180, 255}}) { char drawCallsText[200]; int drawCallsTextLen = snprintf(drawCallsText, sizeof(drawCallsText), "Last frame got: %llu draw calls\n", g_drawCallsDuringLastFrame); if (drawCallsTextLen < 0) drawCallsTextLen = 0; else if ((size_t)drawCallsTextLen >= sizeof(drawCallsText)) drawCallsTextLen = (int)sizeof(drawCallsText) - 1; Clay_String cs = {.isStaticallyAllocated = false, .length = (size_t)drawCallsTextLen, .chars = drawCallsText}; Clay_TextElementConfig drawCallsElementConfig = {.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {255, 255, 255, 255}}; CLAY_TEXT(cs, &drawCallsElementConfig); } CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0)}, .padding = {8, 8, 8, 8}, .childGap = 8}, .backgroundColor = {180, 180, 180, 255}}) { Clay_String cs = {.isStaticallyAllocated = false, .length = g_fpsTextLen, .chars = g_fpsText}; Clay_TextElementConfig fpsElementConfig = {.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {255, 255, 255, 255}}; CLAY_TEXT(cs, &fpsElementConfig); } } CLAY( CLAY_ID("Blob4Floating2"), { .floating = {.attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, .zIndex = 1, .parentId = Clay_GetElementId(CLAY_STRING("SidebarBlob4")).id}, .backgroundColor = {40, 80, 200, 200}, .border = {.width = {0, 10, 0, 10}, .color = {0, 0, 0, 80}}, .cornerRadius = CLAY_CORNER_RADIUS(18), .layout = { .padding = {10, 10, 10, 10}, }, }) { CLAY(CLAY_ID("ScrollContainer"), {.layout = {.sizing = {.height = CLAY_SIZING_FIXED(200)}, .childGap = 2}, .clip = {.vertical = true, .childOffset = Clay_GetScrollOffset()}}) { CLAY(CLAY_ID("FloatingContainer2"), {.layout.sizing.height = CLAY_SIZING_GROW(), .floating = {.attachTo = CLAY_ATTACH_TO_PARENT, .zIndex = 1}}) { CLAY(CLAY_ID("FloatingContainerInner"), { .layout = {.sizing = {.width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW()}, .padding = {16, 16, 16, 16}}, .backgroundColor = {140, 80, 200, 200}, .border = {.width = CLAY_BORDER_OUTSIDE(4), .color = {80, 80, 80, 255}}, .cornerRadius = {30, 3, 3, 30}, }) { CLAY_TEXT(CLAY_STRING("I'm an inline floating container."), CLAY_TEXT_CONFIG({.fontSize = 24, .textColor = {255, 255, 0, 255}})); } } CLAY(CLAY_ID("ScrollContainerInner"), {.layout = {.layoutDirection = CLAY_TOP_TO_BOTTOM}, .backgroundColor = {160, 160, 160, 255}}) { for (int i = 0; i < 100; i++) { RenderDropdownTextItem(i); } } } } Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("MainContent"))); if (scrollData.found) { CLAY(CLAY_ID("ScrollBar"), {.floating = { .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, .offset = {.y = -(scrollData.scrollPosition->y / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height}, .zIndex = 1, .parentId = Clay_GetElementId(CLAY_STRING("MainContent")).id, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP}}}) { CLAY( CLAY_ID("ScrollBarButton"), {.layout = { .sizing = {CLAY_SIZING_FIXED(12), CLAY_SIZING_FIXED((scrollData.scrollContainerDimensions.height / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height)}}, .backgroundColor = Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar"))) ? (Clay_Color){100, 100, 140, 150} : (Clay_Color){120, 120, 160, 150}, .cornerRadius = CLAY_CORNER_RADIUS(6)}) {} } } } return Clay_EndLayout(); } void loop() { LAST = NOW; NOW = SDL_GetPerformanceCounter(); deltaTime = (double)((NOW - LAST) * 1000 / (double)SDL_GetPerformanceFrequency()); glClearColor(0.1f, 0.2f, 0.1f, 1.0f); Clay_Vector2 scrollDelta = {}; SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: { g_ctx.shouldContinue = false; } case SDL_MOUSEWHEEL: { scrollDelta.x = event.wheel.x; scrollDelta.y = event.wheel.y; break; } } } int mouseX = 0; int mouseY = 0; Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY); Clay_Vector2 mousePosition = (Clay_Vector2){(float)mouseX, (float)mouseY}; Clay_SetPointerState(mousePosition, mouseState & SDL_BUTTON(1)); Clay_UpdateScrollContainers( true, (Clay_Vector2){scrollDelta.x, scrollDelta.y}, deltaTime); SDL_GL_GetDrawableSize(g_ctx.sdlWindow, &g_ctx.screenWidth, &g_ctx.screenHeight); glViewport(0, 0, g_ctx.screenWidth, g_ctx.screenHeight); Clay_SetLayoutDimensions((Clay_Dimensions){(float)g_ctx.screenWidth, (float)g_ctx.screenHeight}); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); // Clay renderer is simple and never writes to depth buffer Clay_RenderCommandArray cmds = CreateLayout(); uint64_t drawCalls1 = g_gles3.totalDrawCallsToOpenGl; Gles3_Render(&g_gles3, cmds, g_stbFonts); uint64_t drawCalls2 = g_gles3.totalDrawCallsToOpenGl; g_drawCallsDuringLastFrame = drawCalls2 - drawCalls1; // update FPS counter Uint64 NOW2 = SDL_GetPerformanceCounter(); /* FPS based on simulation delta */ g_timeAccumulator += deltaTime; g_frameCount++; /* wall-clock frame time */ double frameSeconds = (double)(NOW2 - NOW) / (double)SDL_GetPerformanceFrequency(); g_wallTimeAccumulator += frameSeconds; /* update text ONLY every 5 seconds */ double measureInterval = 3000.0; double measuresPerSecond = 1000.0 / measureInterval; if (g_timeAccumulator >= measureInterval) { g_fps = (g_frameCount / g_timeAccumulator) * 1000.0; g_avgFrameMs = (g_wallTimeAccumulator / g_frameCount) * 1000.0; g_fpsTextLen = (size_t)snprintf( (char *)g_fpsText, sizeof(g_fpsText), "FPS: %.3f | Avg frame: %.3f ms", g_fps, g_avgFrameMs); g_timeAccumulator = 0.0; g_wallTimeAccumulator = 0.0; g_frameCount = 0; } SDL_GL_SwapWindow(g_ctx.sdlWindow); } // Just initializes and spins the animation loop int main() { initVideo(&g_ctx, 1280, 720); init(); g_ctx.shouldContinue = true; #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(loop, 0, 1); #else while (g_ctx.shouldContinue) { loop(); } #endif } ================================================ FILE: examples/GLES3-SDL2-video-demo/.gitignore ================================================ /build/ /website-demo-macos-sdl2* ================================================ FILE: examples/GLES3-SDL2-video-demo/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) set(CMAKE_C_STANDARD 99) project(GLES3_SDL2_video_demo C) # ------------------------------------------------- # FetchContent # ------------------------------------------------- include(FetchContent) set(FETCHCONTENT_QUIET FALSE) # ------------------------------------------------- # STB (header-only) # ------------------------------------------------- FetchContent_Declare( stb GIT_REPOSITORY https://github.com/nothings/stb.git GIT_TAG master ) FetchContent_MakeAvailable(stb) # ------------------------------------------------- # SDL2 # ------------------------------------------------- FetchContent_Declare( SDL2 GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" GIT_TAG "release-2.30.10" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(SDL2) # ------------------------------------------------- # Executable # ------------------------------------------------- add_executable(GLES3_SDL2_video_demo main.c) target_compile_options(GLES3_SDL2_video_demo PUBLIC) target_include_directories(GLES3_SDL2_video_demo PUBLIC . # This renderer ../.. # Clay ${stb_SOURCE_DIR} # STB header only depencency that does not have its own CMake build ) # ------------------------------------------------- # Link libraries # ------------------------------------------------- target_link_libraries(GLES3_SDL2_video_demo PUBLIC SDL2::SDL2main SDL2::SDL2-static ) # ------------------------------------------------- # Platform-specific OpenGL / GLES # ------------------------------------------------- find_package(SDL2 REQUIRED) find_library(OPENGL_FRAMEWORK OpenGL) target_link_libraries(GLES3_SDL2_video_demo PRIVATE ${OPENGL_FRAMEWORK} ) # ------------------------------------------------- # Build flags (kept minimal) # ------------------------------------------------- if(MSVC) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") else() set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") endif() # ------------------------------------------------- # Copy resources # ------------------------------------------------- add_custom_command( TARGET GLES3_SDL2_video_demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources ) ================================================ FILE: examples/GLES3-SDL2-video-demo/Makefile.emscripten ================================================ # vim: set tabstop=4 shiftwidth=4 expandtab noexpandtab: # # PROGRAM COMPONENTS # CXX = emcc CXXFLAGS = -std=c99 CXXFLAGS += -O0 CXXFLAGS += -I../.. CXXFLAGS += -I./build/_deps/stb-src LDLIBS += -s USE_ZLIB=1 LDLIBS += -s USE_SDL=2 LDLIBS += -s FULL_ES2=1 -s USE_WEBGL2=1 LDLIBS += -s ALLOW_MEMORY_GROWTH=1 -s GL_UNSAFE_OPTS=0 LDLIBS += -s STACK_SIZE=2048kb LDLIBS += -s EXPORTED_FUNCTIONS=['_main'] LDLIBS += -s ASSERTIONS=1 -s SAFE_HEAP=1 LDLIBS += --preload-file $(PWD)/resources/Roboto-Regular.ttf@resources/Roboto-Regular.ttf main: mkdir -p build/emscripten time $(CXX) $(CXXFLAGS) \ $(PWD)/main.c \ $(LDLIBS) -o build/emscripten/index.html test: make -f Makefile.emscripten main \ && (cd build/emscripten && python3 -mhttp.server) .PHONY: main ================================================ FILE: examples/GLES3-SDL2-video-demo/Makefile.macos ================================================ # vim: set tabstop=4 shiftwidth=4 expandtab noexpandtab: CXX = clang CXXFLAGS = -std=c99 CXXFLAGS += -g -O0 -fno-omit-frame-pointer CXXFLAGS += -ferror-limit=1 CXXFLAGS += -I../.. CXXFLAGS += -I./build/_deps/stb-src CXXFLAGS += -DGL_SILENCE_DEPRECATION # SDL2 (Homebrew) CXXFLAGS += -I$(shell brew --prefix sdl2)/include/SDL2 LDLIBS += -L$(shell brew --prefix sdl2)/lib -lSDL2 # macOS system frameworks (OpenGL needs these) LDLIBS += -framework OpenGL LDLIBS += -framework Cocoa LDLIBS += -framework IOKit LDLIBS += -framework CoreVideo main: mkdir -p build time $(CXX) $(CXXFLAGS) \ $(PWD)/main.c \ $(LDLIBS) \ -o build/website-demo-macos-sdl2 ================================================ FILE: examples/GLES3-SDL2-video-demo/README.md ================================================ GLES3 Renderer Video Demo (Using SDL2) ====================================== This directory contains a standard Video-Demo example using work-in-progress GLES3 renderer. While it still needs refinement, the renderer is already functional and demonstrates the core rendering pipeline. Current features - Supports all draw commands except custom. - In the best-case scenario (no clipping): - All quad-based commands (Rectangle, Image, Border) are rendered in a single draw call. - All glyphs belonging to the same font are rendered in one instanced draw call. - When clipping (scissoring) is used: - The renderer flushes draw calls before and after each scissor region. - Supports up to 4 fonts and 4 image textures. - Image textures may also be used as texture atlases. - Custom UserData provides per-image UV coordinates, allowing multiple images to share a single OpenGL texture. - Uses stb_image.h and stb_truetype.h as single-header dependencies for asset loading. - The loading layer is modular and can be replaced with a different asset pipeline if needed. Currently builds on: - Emscripten - clang++ / macOS - CMake support is not available yet. Windowing and platform support This example uses SDL2, but the renderer is framework agnostic. For sake of example you can also build it with hand-crafted Makefile.macos make -f Makefile.emscripten test and then navigate to http://localhost:8080 On Emscripten it works well. ================================================ FILE: examples/GLES3-SDL2-video-demo/main.c ================================================ #include #define STB_IMAGE_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION #define CLAY_IMPLEMENTATION #define CLAY_RENDERER_GLES3_IMPLEMENTATION #include #include "../../renderers/GLES3/clay_renderer_gles3.h" #include "../shared-layouts/clay-video-demo.c" #include "../../renderers/GLES3/clay_renderer_gles3_loader_stb.c" typedef struct VideoCtx { int shouldContinue; SDL_Window *sdlWindow; SDL_GLContext sdlContext; int screenWidth, screenHeight; } VideoCtx; VideoCtx g_ctx; static int initVideo(VideoCtx *ctx, const int initialWidth, const int initialHeight) { SDL_Init(SDL_INIT_VIDEO); #if defined(__EMSCRIPTEN__) // OpenGL ES 3 profile SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #else // Apple MacOs will use it own legacy desktop GL instead // I know, I lied, I said this was an GLES3 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); #endif SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); g_ctx.sdlWindow = SDL_CreateWindow( "SDL2 GLES3", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, initialWidth, initialHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN ); g_ctx.sdlContext = SDL_GL_CreateContext(g_ctx.sdlWindow); SDL_ShowWindow(g_ctx.sdlWindow); SDL_Delay(1); SDL_GL_GetDrawableSize(g_ctx.sdlWindow, &g_ctx.screenWidth, &g_ctx.screenHeight); glViewport(0, 0, g_ctx.screenWidth, g_ctx.screenHeight); glEnable(GL_BLEND); // Enables blending, which allows transparent textures to be rendered properly. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Sets the blending function. // - `GL_SRC_ALPHA`: Uses the alpha value of the source (texture or color). // - `GL_ONE_MINUS_SRC_ALPHA`: Makes the destination color blend with the background based on alpha. // This is commonly used for standard transparency effects. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); // Enables depth testing, ensuring that objects closer to the camera are drawn in front of those farther away. // This prevents objects from rendering incorrectly based on draw order. return 1; } void My_ErrorHandler(Clay_ErrorData errorData) { printf("[ClaY ErroR] %s", errorData.errorText.chars); } Stb_FontData g_stbFonts[MAX_FONTS]; // Fonts userData Gles3_Renderer g_gles3; // The renderer itself Uint64 NOW = 0; Uint64 LAST = 0; double deltaTime = 0; // is executed before everything void init() { size_t clayRequiredMemory = Clay_MinMemorySize(); g_gles3.clayMemory = (Clay_Arena){ .capacity = clayRequiredMemory, .memory = (char *)malloc(clayRequiredMemory), }; Clay_Context *clayCtx = Clay_Initialize( g_gles3.clayMemory, (Clay_Dimensions){ .width = (float)g_ctx.screenWidth, .height = (float)g_ctx.screenHeight, }, (Clay_ErrorHandler){ .errorHandlerFunction = My_ErrorHandler, }); // Note that MeasureText has to be set after the Context is set! Clay_SetCurrentContext(clayCtx); Clay_SetMeasureTextFunction(Stb_MeasureText, &g_stbFonts); Gles3_SetRenderTextFunction(&g_gles3, Stb_RenderText, &g_stbFonts); Gles3_Initialize(&g_gles3, 4096); int atlasW = 1024; int atlasH = 1024; if (!Stb_LoadFont( &g_gles3.fontTextures[0], &g_stbFonts[0], "resources/Roboto-Regular.ttf", 24.0f, // bake pixel height atlasW, atlasH)) abort(); Clay_SetDebugModeEnabled(true); } void loop() { glClearColor(0.1f, 0.2f, 0.1f, 1.0f); Clay_Vector2 scrollDelta = {}; SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: { g_ctx.shouldContinue = false; } case SDL_MOUSEWHEEL: { scrollDelta.x = event.wheel.x; scrollDelta.y = event.wheel.y; break; } } } LAST = NOW; NOW = SDL_GetPerformanceCounter(); deltaTime = (double)((NOW - LAST) * 1000 / (double)SDL_GetPerformanceFrequency()); int mouseX = 0; int mouseY = 0; Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY); Clay_Vector2 mousePosition = (Clay_Vector2){(float)mouseX, (float)mouseY}; Clay_SetPointerState(mousePosition, mouseState & SDL_BUTTON(1)); Clay_UpdateScrollContainers( true, (Clay_Vector2){scrollDelta.x, scrollDelta.y}, deltaTime); SDL_GL_GetDrawableSize(g_ctx.sdlWindow, &g_ctx.screenWidth, &g_ctx.screenHeight); glViewport(0, 0, g_ctx.screenWidth, g_ctx.screenHeight); Clay_SetLayoutDimensions((Clay_Dimensions){(float)g_ctx.screenWidth, (float)g_ctx.screenHeight}); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); // Clay renderer is simple and never writes to depth buffer ClayVideoDemo_Data data = ClayVideoDemo_Initialize(); Clay_RenderCommandArray cmds = ClayVideoDemo_CreateLayout(&data); Gles3_Render(&g_gles3, cmds, g_stbFonts); SDL_GL_SwapWindow(g_ctx.sdlWindow); } // Just initializes and spins the animation loop int main() { initVideo(&g_ctx, 1280, 720); init(); g_ctx.shouldContinue = true; #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(loop, 0, 1); #else while (g_ctx.shouldContinue) { loop(); } #endif } ================================================ FILE: examples/SDL2-video-demo/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(SDL2_video_demo C) set(CMAKE_C_STANDARD 99) include(FetchContent) set(FETCHCONTENT_QUIET FALSE) FetchContent_Declare( SDL2 GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" GIT_TAG "release-2.30.10" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(SDL2) FetchContent_Declare( SDL2_ttf GIT_REPOSITORY "https://github.com/libsdl-org/SDL_ttf.git" GIT_TAG "release-2.22.0" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(SDL2_ttf) FetchContent_Declare( SDL2_image GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git" GIT_TAG "release-2.8.4" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(SDL2_image) add_executable(SDL2_video_demo main.c) target_compile_options(SDL2_video_demo PUBLIC) target_include_directories(SDL2_video_demo PUBLIC .) target_link_libraries(SDL2_video_demo PUBLIC SDL2::SDL2main SDL2::SDL2-static SDL2_ttf::SDL2_ttf-static SDL2_image::SDL2_image-static ) if(MSVC) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") else() set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") endif() add_custom_command( TARGET SDL2_video_demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources ) ================================================ FILE: examples/SDL2-video-demo/main.c ================================================ #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "../../renderers/SDL2/clay_renderer_SDL2.c" #include #include #include #include #include #include #include "../shared-layouts/clay-video-demo.c" SDL_Surface *sample_image; void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); } struct ResizeRenderData_ { SDL_Window* window; int windowWidth; int windowHeight; ClayVideoDemo_Data demoData; SDL_Renderer* renderer; SDL2_Font* fonts; }; typedef struct ResizeRenderData_ ResizeRenderData; int resizeRendering(void* userData, SDL_Event* event) { ResizeRenderData *actualData = userData; if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_EXPOSED) { SDL_Window* window = actualData->window; int windowWidth = actualData->windowWidth; int windowHeight = actualData->windowHeight; ClayVideoDemo_Data demoData = actualData->demoData; SDL_Renderer* renderer = actualData->renderer; SDL2_Font* fonts = actualData->fonts; SDL_GetWindowSize(window, &windowWidth, &windowHeight); Clay_SetLayoutDimensions((Clay_Dimensions) { (float)windowWidth, (float)windowHeight }); Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); Clay_SDL2_Render(renderer, renderCommands, fonts); SDL_RenderPresent(renderer); } return 0; } int main(int argc, char *argv[]) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "Error: could not initialize SDL: %s\n", SDL_GetError()); return 1; } if (TTF_Init() < 0) { fprintf(stderr, "Error: could not initialize TTF: %s\n", TTF_GetError()); return 1; } if (IMG_Init(IMG_INIT_PNG) < 0) { fprintf(stderr, "Error: could not initialize IMG: %s\n", IMG_GetError()); return 1; } TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 16); if (!font) { fprintf(stderr, "Error: could not load font: %s\n", TTF_GetError()); return 1; } SDL2_Font fonts[1] = {}; fonts[FONT_ID_BODY_16] = (SDL2_Font) { .fontId = FONT_ID_BODY_16, .font = font, }; sample_image = IMG_Load("resources/sample.png"); SDL_Window *window = NULL; SDL_Renderer *renderer = NULL; SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); //for antialiasing window = SDL_CreateWindow("SDL", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); //for antialiasing bool enableVsync = false; if(enableVsync){ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);} //"SDL_RENDERER_ACCELERATED" is for antialiasing else{renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);} SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); //for alpha blending uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); int windowWidth = 0; int windowHeight = 0; SDL_GetWindowSize(window, &windowWidth, &windowHeight); Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)windowWidth, (float)windowHeight }, (Clay_ErrorHandler) { HandleClayErrors }); Clay_SetMeasureTextFunction(SDL2_MeasureText, &fonts); Uint64 NOW = SDL_GetPerformanceCounter(); Uint64 LAST = 0; double deltaTime = 0; ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize(); ResizeRenderData userData = { window, // SDL_Window* windowWidth, // int windowHeight, // int demoData, // CustomShit renderer, // SDL_Renderer* fonts // SDL2_Font[1] }; // add an event watcher that will render the screen while youre dragging the window to different sizes SDL_AddEventWatch(resizeRendering, &userData); while (true) { Clay_Vector2 scrollDelta = {}; SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: { goto quit; } case SDL_MOUSEWHEEL: { scrollDelta.x = event.wheel.x; scrollDelta.y = event.wheel.y; break; } } } LAST = NOW; NOW = SDL_GetPerformanceCounter(); deltaTime = (double)((NOW - LAST)*1000 / (double)SDL_GetPerformanceFrequency() ); printf("%f\n", deltaTime); int mouseX = 0; int mouseY = 0; Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY); Clay_Vector2 mousePosition = (Clay_Vector2){ (float)mouseX, (float)mouseY }; Clay_SetPointerState(mousePosition, mouseState & SDL_BUTTON(1)); Clay_UpdateScrollContainers( true, (Clay_Vector2) { scrollDelta.x, scrollDelta.y }, deltaTime ); SDL_GetWindowSize(window, &windowWidth, &windowHeight); Clay_SetLayoutDimensions((Clay_Dimensions) { (float)windowWidth, (float)windowHeight }); Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); Clay_SDL2_Render(renderer, renderCommands, fonts); SDL_RenderPresent(renderer); } quit: SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); IMG_Quit(); TTF_Quit(); SDL_Quit(); return 0; } ================================================ FILE: examples/SDL3-simple-demo/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) # Project setup project(clay_examples_sdl3_simple_demo C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") include(FetchContent) set(FETCHCONTENT_QUIET FALSE) # Download SDL3 FetchContent_Declare( SDL GIT_REPOSITORY https://github.com/libsdl-org/SDL.git GIT_TAG release-3.2.4 GIT_SHALLOW TRUE GIT_PROGRESS TRUE ) message(STATUS "Using SDL via FetchContent") FetchContent_MakeAvailable(SDL) set_property(DIRECTORY "${sdl_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) # Download SDL_ttf FetchContent_Declare( SDL_ttf GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git GIT_TAG release-3.2.2 GIT_SHALLOW TRUE GIT_PROGRESS TRUE ) message(STATUS "Using SDL_ttf via FetchContent") FetchContent_MakeAvailable(SDL_ttf) set_property(DIRECTORY "${sdl_ttf_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) # Download SDL_image FetchContent_Declare( SDL_image GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git" GIT_TAG release-3.2.0 GIT_SHALLOW TRUE GIT_PROGRESS TRUE ) message(STATUS "Using SDL_image via FetchContent") FetchContent_MakeAvailable(SDL_image) set_property(DIRECTORY "${SDL_image_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) # Example executable add_executable(${PROJECT_NAME} main.c) target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image ) add_custom_command( TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources ) ================================================ FILE: examples/SDL3-simple-demo/main.c ================================================ #define SDL_MAIN_USE_CALLBACKS #include #include #include #define CLAY_IMPLEMENTATION #include "../../clay.h" #include #include "../../renderers/SDL3/clay_renderer_SDL3.c" #include "../shared-layouts/clay-video-demo.c" static const Uint32 FONT_ID = 0; static const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; static const Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255}; static const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; typedef struct app_state { SDL_Window *window; Clay_SDL3RendererData rendererData; ClayVideoDemo_Data demoData; } AppState; SDL_Texture *sample_image; bool show_demo = true; static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { TTF_Font **fonts = userData; TTF_Font *font = fonts[config->fontId]; int width, height; TTF_SetFontSize(font, config->fontSize); if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError()); } return (Clay_Dimensions) { (float) width, (float) height }; } void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); } Clay_RenderCommandArray ClayImageSample_CreateLayout() { Clay_BeginLayout(); Clay_Sizing layoutExpand = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }; CLAY(CLAY_ID("OuterContainer"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = layoutExpand, .padding = CLAY_PADDING_ALL(16), .childGap = 16 } }) { CLAY(CLAY_ID("SampleImage"), { .layout = { .sizing = layoutExpand }, .aspectRatio = { 23.0 / 42.0 }, .image = { .imageData = sample_image, } }); } return Clay_EndLayout(); } SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { (void) argc; (void) argv; if (!TTF_Init()) { return SDL_APP_FAILURE; } AppState *state = SDL_calloc(1, sizeof(AppState)); if (!state) { return SDL_APP_FAILURE; } *appstate = state; if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->rendererData.renderer)) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window and renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } SDL_SetWindowResizable(state->window, true); state->rendererData.textEngine = TTF_CreateRendererTextEngine(state->rendererData.renderer); if (!state->rendererData.textEngine) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create text engine from renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } state->rendererData.fonts = SDL_calloc(1, sizeof(TTF_Font *)); if (!state->rendererData.fonts) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to allocate memory for the font array: %s", SDL_GetError()); return SDL_APP_FAILURE; } TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 24); if (!font) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load font: %s", SDL_GetError()); return SDL_APP_FAILURE; } state->rendererData.fonts[FONT_ID] = font; sample_image = IMG_LoadTexture(state->rendererData.renderer, "resources/sample.png"); if (!sample_image) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load image: %s", SDL_GetError()); return SDL_APP_FAILURE; } /* Initialize Clay */ uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = (Clay_Arena) { .memory = SDL_malloc(totalMemorySize), .capacity = totalMemorySize }; int width, height; SDL_GetWindowSize(state->window, &width, &height); Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors }); Clay_SetMeasureTextFunction(SDL_MeasureText, state->rendererData.fonts); state->demoData = ClayVideoDemo_Initialize(); *appstate = state; return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { SDL_AppResult ret_val = SDL_APP_CONTINUE; switch (event->type) { case SDL_EVENT_QUIT: ret_val = SDL_APP_SUCCESS; break; case SDL_EVENT_KEY_UP: if (event->key.scancode == SDL_SCANCODE_SPACE) { show_demo = !show_demo; } break; case SDL_EVENT_WINDOW_RESIZED: Clay_SetLayoutDimensions((Clay_Dimensions) { (float) event->window.data1, (float) event->window.data2 }); break; case SDL_EVENT_MOUSE_MOTION: Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y }, event->motion.state & SDL_BUTTON_LMASK); break; case SDL_EVENT_MOUSE_BUTTON_DOWN: Clay_SetPointerState((Clay_Vector2) { event->button.x, event->button.y }, event->button.button == SDL_BUTTON_LEFT); break; case SDL_EVENT_MOUSE_WHEEL: Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->wheel.x, event->wheel.y }, 0.01f); break; default: break; }; return ret_val; } SDL_AppResult SDL_AppIterate(void *appstate) { AppState *state = appstate; Clay_RenderCommandArray render_commands = (show_demo ? ClayVideoDemo_CreateLayout(&state->demoData) : ClayImageSample_CreateLayout() ); SDL_SetRenderDrawColor(state->rendererData.renderer, 0, 0, 0, 255); SDL_RenderClear(state->rendererData.renderer); SDL_Clay_RenderClayCommands(&state->rendererData, &render_commands); SDL_RenderPresent(state->rendererData.renderer); return SDL_APP_CONTINUE; } void SDL_AppQuit(void *appstate, SDL_AppResult result) { (void) result; if (result != SDL_APP_SUCCESS) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Application failed to run"); } AppState *state = appstate; if (sample_image) { SDL_DestroyTexture(sample_image); } if (state) { if (state->rendererData.renderer) SDL_DestroyRenderer(state->rendererData.renderer); if (state->window) SDL_DestroyWindow(state->window); if (state->rendererData.fonts) { for(size_t i = 0; i < sizeof(state->rendererData.fonts) / sizeof(*state->rendererData.fonts); i++) { TTF_CloseFont(state->rendererData.fonts[i]); } SDL_free(state->rendererData.fonts); } if (state->rendererData.textEngine) TTF_DestroyRendererTextEngine(state->rendererData.textEngine); SDL_free(state); } TTF_Quit(); } ================================================ FILE: examples/cairo-pdf-rendering/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(clay_examples_cairo_pdf_rendering C) set(CMAKE_C_STANDARD 99) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake") add_executable(clay_examples_cairo_pdf_rendering main.c) find_package(Cairo REQUIRED) target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC) target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC . ${CAIRO_INCLUDE_DIRS}) target_link_libraries(clay_examples_cairo_pdf_rendering PUBLIC Cairo::Cairo) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") add_custom_command( TARGET clay_examples_cairo_pdf_rendering POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources) ================================================ FILE: examples/cairo-pdf-rendering/main.c ================================================ // Copyright (c) 2024 Justin Andreas Lacoste (@27justin) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the // use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software in a // product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // // SPDX-License-Identifier: Zlib #include // The renderer includes clay.h while also providing the // CLAY_IMPLEMENTATION #include "../../renderers/cairo/clay_renderer_cairo.c" // cairo-pdf, though this is optional and not required if you, // e.g. render PNGs. #include const uint16_t FONT_CALLISTOGA = 0; const uint16_t FONT_QUICKSAND = 0; // Layout the first page. void Layout() { static Clay_Color PRIMARY = { 0xa8, 0x42, 0x1c, 255 }; static Clay_Color BACKGROUND = { 0xF4, 0xEB, 0xE6, 255 }; static Clay_Color ACCENT = { 0xFA, 0xE0, 0xD4, 255 }; CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = BACKGROUND }) { CLAY(CLAY_ID("PageMargins"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .padding = { 70, 70, 50, 50 }, // Some nice looking page margins .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 10} }) { // Section Title CLAY_TEXT(CLAY_STRING("Features Overview"), CLAY_TEXT_CONFIG({ .fontId = FONT_CALLISTOGA, .textColor = PRIMARY, .fontSize = 24 })); // Feature Box CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }, .childGap = 10 }}) { CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }}, .backgroundColor = ACCENT, .cornerRadius = CLAY_CORNER_RADIUS(12) }) { CLAY_AUTO_ID({ .layout = {.padding = CLAY_PADDING_ALL(20), .childGap = 4, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) { CLAY_TEXT(CLAY_STRING("- High performance"), CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); CLAY_TEXT(CLAY_STRING("- Declarative syntax"), CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); CLAY_TEXT(CLAY_STRING("- Flexbox-style responsive layout"), CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); CLAY_TEXT(CLAY_STRING("- Single .h file for C/C++"), CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); CLAY_TEXT(CLAY_STRING("- And now with cairo!"), CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); } } CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(10), .layoutDirection = CLAY_TOP_TO_BOTTOM, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }, .childGap = 4 }, .backgroundColor = ACCENT, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { // Profile picture CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)}, .padding = { 30, 30, 0, 0 }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }}, .border = { .color = PRIMARY, .width = 2, 2, 2, 2 }, .cornerRadius = 10 }) { CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }, .image = { .imageData = "resources/check.png" }}); } } } CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(16) } }}); CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childGap = 10, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) { CLAY_TEXT(CLAY_STRING("Cairo"), CLAY_TEXT_CONFIG({ .fontId = FONT_CALLISTOGA, .fontSize = 24, .textColor = PRIMARY })); CLAY_AUTO_ID({ .layout = { .padding = CLAY_PADDING_ALL(10) }, .backgroundColor = ACCENT, .cornerRadius = 10 }) { CLAY_TEXT(CLAY_STRING("Officiis quia quia qui inventore ratione voluptas et. Quidem sunt unde similique. Qui est et exercitationem cumque harum illum. Numquam placeat aliquid quo voluptatem. " "Deleniti saepe nihil exercitationem nemo illo. Consequatur beatae repellat provident similique. Provident qui exercitationem deserunt sapiente. Quam qui dolor corporis odit. " "Assumenda corrupti sunt culpa pariatur. Vero sit ut minima. In est consequatur minus et cum sint illum aperiam. Qui ipsa quas nisi omnis aut quia nobis. " "Corporis deserunt eum mollitia modi rerum voluptas. Expedita non ab esse. Sit voluptates eos voluptatem labore aspernatur quia eum. Modi cumque atque non. Sunt officiis corrupti neque ut inventore excepturi rem minima. Possimus sed soluta qui ea aut ipsum laborum fugit. " "Voluptate eum consectetur non. Quo autem voluptate soluta atque dolorum maxime. Officiis inventore omnis eveniet beatae ipsa optio. Unde voluptatum ut autem quia sit sit et. Ut inventore qui quia totam consequatur. Sit ea consequatur omnis rerum nulla aspernatur deleniti."), CLAY_TEXT_CONFIG({ .fontId = FONT_QUICKSAND, .fontSize = 16, .textColor = PRIMARY, .lineHeight = 16 })); } } } } } void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); } int main(void) { // First we set up our cairo surface. // In this example we will use the PDF backend, // but you should be able to use any of them. // Guaranteed to be working are: PDF, PNG // Create a PDF surface that is the same size as a DIN A4 sheet // When using the PDF backend, cairo calculates in points (1 point == 1/72.0 inch) double width = (21.0 / 2.54) * 72, // cm in points height = (29.7 / 2.54) * 72; cairo_surface_t *surface = cairo_pdf_surface_create("output.pdf", width, height); cairo_t *cr = cairo_create(surface); cairo_surface_destroy(surface); // Drop reference // Initialize internal global variable with `cr`. // We require some kind of global reference to a valid // cairo instance to properly measure text. // Note that due to this, this interface is not thread-safe! Clay_Cairo_Initialize(cr); uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); // We initialize Clay with the same size Clay_Initialize(clayMemory, (Clay_Dimensions) { width, height }, (Clay_ErrorHandler) { HandleClayErrors }); char** fonts = (char*[]) { "Callistoga", "Quicksand Semibold" }; Clay_SetMeasureTextFunction(Clay_Cairo_MeasureText, fonts); Clay_BeginLayout(); // Here you can now create the declarative clay layout. // Moved into a separate function for brevity. Layout(); Clay_RenderCommandArray commands = Clay_EndLayout(); // Pass our layout to the cairo backend Clay_Cairo_Render(commands, fonts); // To keep this example short, we will not emit a second page in the PDF. // But to do so, you have to // 1. cairo_show_page(cr) // 2. Clay_BeginLayout(); // 3. Create your layout // 4. commands = Clay_EndLayout(); // 5. Clay_Cairo_Render(commands); cairo_destroy(cr); return 0; } ================================================ FILE: examples/clay-official-website/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(clay_official_website C) set(CMAKE_C_STANDARD 99) add_executable(clay_official_website main.c) target_compile_options(clay_official_website PUBLIC) target_include_directories(clay_official_website PUBLIC .) ================================================ FILE: examples/clay-official-website/build/clay/index.html ================================================ Clay - UI Layout Library ================================================ FILE: examples/clay-official-website/build.sh ================================================ mkdir -p build/clay \ && clang \ -Wall \ -Werror \ -Os \ -DCLAY_WASM \ -mbulk-memory \ --target=wasm32 \ -nostdlib \ -Wl,--strip-all \ -Wl,--export-dynamic \ -Wl,--no-entry \ -Wl,--export=__heap_base \ -Wl,--export=ACTIVE_RENDERER_INDEX \ -Wl,--initial-memory=6553600 \ -o build/clay/index.wasm \ main.c \ && cp index.html build/index.html && cp -r fonts/ build/clay/fonts \ && cp -r images/ build/clay/images ================================================ FILE: examples/clay-official-website/index.html ================================================ Clay - UI Layout Library ================================================ FILE: examples/clay-official-website/main.c ================================================ #define CLAY_IMPLEMENTATION #include "../../clay.h" double windowWidth = 1024, windowHeight = 768; float modelPageOneZRotation = 0; uint32_t ACTIVE_RENDERER_INDEX = 0; const uint32_t FONT_ID_BODY_16 = 0; const uint32_t FONT_ID_TITLE_56 = 1; const uint32_t FONT_ID_BODY_24 = 2; const uint32_t FONT_ID_BODY_36 = 3; const uint32_t FONT_ID_TITLE_36 = 4; const uint32_t FONT_ID_MONOSPACE_24 = 5; const Clay_Color COLOR_LIGHT = (Clay_Color) {244, 235, 230, 255}; const Clay_Color COLOR_LIGHT_HOVER = (Clay_Color) {224, 215, 210, 255}; const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; const Clay_Color COLOR_RED_HOVER = (Clay_Color) {148, 46, 8, 255}; const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; const Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255}; // Colors for top stripe const Clay_Color COLOR_TOP_BORDER_1 = (Clay_Color) {168, 66, 28, 255}; const Clay_Color COLOR_TOP_BORDER_2 = (Clay_Color) {223, 110, 44, 255}; const Clay_Color COLOR_TOP_BORDER_3 = (Clay_Color) {225, 138, 50, 255}; const Clay_Color COLOR_TOP_BORDER_4 = (Clay_Color) {236, 189, 80, 255}; const Clay_Color COLOR_TOP_BORDER_5 = (Clay_Color) {240, 213, 137, 255}; const Clay_Color COLOR_BLOB_BORDER_1 = (Clay_Color) {168, 66, 28, 255}; const Clay_Color COLOR_BLOB_BORDER_2 = (Clay_Color) {203, 100, 44, 255}; const Clay_Color COLOR_BLOB_BORDER_3 = (Clay_Color) {225, 138, 50, 255}; const Clay_Color COLOR_BLOB_BORDER_4 = (Clay_Color) {236, 159, 70, 255}; const Clay_Color COLOR_BLOB_BORDER_5 = (Clay_Color) {240, 189, 100, 255}; #define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = (vector).x, .y = (vector).y } Clay_TextElementConfig headerTextConfig = (Clay_TextElementConfig) { .fontId = 2, .fontSize = 24, .textColor = {61, 26, 5, 255} }; Clay_TextElementConfig blobTextConfig = (Clay_TextElementConfig) { .fontId = 2, .fontSize = 30, .textColor = {244, 235, 230, 255} }; typedef struct { void* memory; uintptr_t offset; } Arena; Arena frameArena = {}; typedef struct d { Clay_String link; bool cursorPointer; bool disablePointerEvents; } CustomHTMLData; CustomHTMLData* FrameAllocateCustomData(CustomHTMLData data) { CustomHTMLData *customData = (CustomHTMLData *)(frameArena.memory + frameArena.offset); *customData = data; frameArena.offset += sizeof(CustomHTMLData); return customData; } Clay_String* FrameAllocateString(Clay_String string) { Clay_String *allocated = (Clay_String *)(frameArena.memory + frameArena.offset); *allocated = string; frameArena.offset += sizeof(Clay_String); return allocated; } void LandingPageBlob(int index, int fontSize, Clay_Color color, Clay_String text, Clay_String imageURL) { CLAY(CLAY_IDI("HeroBlob", index), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }, .border = { .color = color, .width = { 2, 2, 2, 2 }}, .cornerRadius = CLAY_CORNER_RADIUS(10) }) { CLAY(CLAY_IDI("CheckImage", index), { .layout = { .sizing = { CLAY_SIZING_FIXED(32) } }, .aspectRatio = { 1 }, .image = { .imageData = FrameAllocateString(imageURL) } }) {} CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = fontSize, .fontId = FONT_ID_BODY_24, .textColor = color })); } } void LandingPageDesktop() { CLAY(CLAY_ID("LandingPage1Desktop"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { 50, 50 } } }) { CLAY(CLAY_ID("LandingPage1"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(32), .childGap = 32 }, .border = { .width = { .left = 2, .right = 2 }, .color = COLOR_RED } }) { CLAY(CLAY_ID("LeftText"), { .layout = { .sizing = { .width = CLAY_SIZING_PERCENT(0.55f) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG({ .fontSize = 56, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED })); CLAY(CLAY_ID("LandingPageSpacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(32) } } }) {} CLAY_TEXT(CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE })); } CLAY(CLAY_ID("HeroImageOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_PERCENT(0.45f) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16 } }) { LandingPageBlob(1, 32, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png")); LandingPageBlob(2, 32, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png")); LandingPageBlob(3, 32, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png")); LandingPageBlob(4, 32, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png")); LandingPageBlob(5, 32, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png")); } } } } void LandingPageMobile() { CLAY(CLAY_ID("LandingPage1Mobile"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 16, 32, 32 }, .childGap = 32 } }) { CLAY(CLAY_ID("LeftText"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED })); CLAY(CLAY_ID("LandingPageSpacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(32) } } }) {} CLAY_TEXT(CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG({ .fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE })); } CLAY(CLAY_ID("HeroImageOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16 } }) { LandingPageBlob(1, 28, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png")); LandingPageBlob(2, 28, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png")); LandingPageBlob(3, 28, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png")); LandingPageBlob(4, 28, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png")); LandingPageBlob(5, 28, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png")); } } } void FeatureBlocksDesktop() { CLAY(CLAY_ID("FeatureBlocksOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) } } }) { CLAY(CLAY_ID("FeatureBlocksInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED } }) { Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED }); CLAY(CLAY_ID("HFileBoxOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 50, 32, 32}, .childGap = 8 } }) { CLAY(CLAY_ID("HFileIncludeOuter"), { .layout = { .padding = {8, 4} }, .backgroundColor = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { CLAY_TEXT(CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT })); } CLAY_TEXT(CLAY_STRING("~2000 lines of C99."), textConfig); CLAY_TEXT(CLAY_STRING("Zero dependencies, including no C standard library."), textConfig); } CLAY(CLAY_ID("BringYourOwnRendererOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 50, 32, 32}, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE })); CLAY_TEXT(CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig); CLAY_TEXT(CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig); } } } } void FeatureBlocksMobile() { CLAY(CLAY_ID("FeatureBlocksInner"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) } }, .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED } }) { Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED }); CLAY(CLAY_ID("HFileBoxOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 8 } }) { CLAY(CLAY_ID("HFileIncludeOuter"), { .layout = { .padding = {8, 4} }, .backgroundColor = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { CLAY_TEXT(CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT })); } CLAY_TEXT(CLAY_STRING("~2000 lines of C99."), textConfig); CLAY_TEXT(CLAY_STRING("Zero dependencies, including no C standard library."), textConfig); } CLAY(CLAY_ID("BringYourOwnRendererOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE })); CLAY_TEXT(CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig); CLAY_TEXT(CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig); } } } void DeclarativeSyntaxPageDesktop() { CLAY(CLAY_ID("SyntaxPageDesktop"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = { 50, 50 } } }) { CLAY(CLAY_ID("SyntaxPage"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = CLAY_PADDING_ALL(32), .childGap = 32 }, .border = { .width = { .left = 2, .right = 2 }, .color = COLOR_RED }}) { CLAY(CLAY_ID("SyntaxPageLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED })); CLAY(CLAY_ID("SyntaxSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) } } }) {} CLAY_TEXT(CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); CLAY_TEXT(CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); } CLAY(CLAY_ID("SyntaxPageRightImage"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) { CLAY(CLAY_ID("SyntaxPageRightImageInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .aspectRatio = { 1136.0 / 1194.0 }, .image = { .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {} } } } } void DeclarativeSyntaxPageMobile() { CLAY(CLAY_ID("SyntaxPageDesktop"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 16 } }) { CLAY(CLAY_ID("SyntaxPageLeftText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED })); CLAY(CLAY_ID("SyntaxSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) } } }) {} CLAY_TEXT(CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); CLAY_TEXT(CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); } CLAY(CLAY_ID("SyntaxPageRightImage"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) { CLAY(CLAY_ID("SyntaxPageRightImageInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .aspectRatio = { 1136.0 / 1194.0 }, .image = { .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {} } } } Clay_Color ColorLerp(Clay_Color a, Clay_Color b, float amount) { return (Clay_Color) { .r = a.r + (b.r - a.r) * amount, .g = a.g + (b.g - a.g) * amount, .b = a.b + (b.b - a.b) * amount, .a = a.a + (b.a - a.a) * amount, }; } Clay_String LOREM_IPSUM_TEXT = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); void HighPerformancePageDesktop(float lerpValue) { CLAY(CLAY_ID("PerformanceOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {82, 82, 32, 32}, .childGap = 64 }, .backgroundColor = COLOR_RED }) { CLAY(CLAY_ID("PerformanceLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT })); CLAY(CLAY_ID("PerformanceSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {} CLAY_TEXT(CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT })); CLAY_TEXT(CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT })); CLAY_TEXT(CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT })); } CLAY(CLAY_ID("PerformanceRightImageOuter"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) { CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(400) } }, .border = { .width = {2, 2, 2, 2}, .color = COLOR_LIGHT } }) { CLAY(CLAY_ID("AnimationDemoContainerLeft"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.3f + 0.4f * lerpValue), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(32) }, .backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue) }) { CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT })); } CLAY(CLAY_ID("AnimationDemoContainerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(32) }, .backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue) }) { CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT })); } } } } } void HighPerformancePageMobile(float lerpValue) { CLAY(CLAY_ID("PerformanceOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 32 }, .backgroundColor = COLOR_RED }) { CLAY(CLAY_ID("PerformanceLeftText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT })); CLAY(CLAY_ID("PerformanceSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {} CLAY_TEXT(CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT })); CLAY_TEXT(CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT })); CLAY_TEXT(CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT })); } CLAY(CLAY_ID("PerformanceRightImageOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) { CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(400) } }, .border = { .width = { 2, 2, 2, 2 }, .color = COLOR_LIGHT }}) { CLAY(CLAY_ID("AnimationDemoContainerLeft"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.35f + 0.3f * lerpValue), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(16) }, .backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue) }) { CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT })); } CLAY(CLAY_ID("AnimationDemoContainerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(16) }, .backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue) }) { CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT })); } } } } } void HandleRendererButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData) { if (pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { ACTIVE_RENDERER_INDEX = (uint32_t)userData; Clay_SetCullingEnabled(ACTIVE_RENDERER_INDEX == 1); Clay_SetExternalScrollHandlingEnabled(ACTIVE_RENDERER_INDEX == 0); } } void RendererButtonActive(Clay_String text) { CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIXED(300) }, .padding = CLAY_PADDING_ALL(16) }, .backgroundColor = Clay_Hovered() ? COLOR_RED_HOVER : COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(10), .userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true }) }) { CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT })); } } void RendererButtonInactive(Clay_String text, size_t rendererIndex) { CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIXED(300)}, .padding = CLAY_PADDING_ALL(16) }, .border = { .width = {2, 2, 2, 2}, .color = COLOR_RED }, .backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT, .cornerRadius = CLAY_CORNER_RADIUS(10), .userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true }) }) { Clay_OnHover(HandleRendererButtonInteraction, (void *)rendererIndex); CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); } } void RendererPageDesktop() { CLAY(CLAY_ID("RendererPageDesktop"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = { 50, 50 } } }) { CLAY(CLAY_ID("RendererPage"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = CLAY_PADDING_ALL(32), .childGap = 32 }, .border = { .width = { .left = 2, .right = 2 }, .color = COLOR_RED } }) { CLAY(CLAY_ID("RendererLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED })); CLAY(CLAY_ID("RendererSpacerLeft"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {} CLAY_TEXT(CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); CLAY_TEXT(CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); CLAY_TEXT(CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); } CLAY(CLAY_ID("RendererRightText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .childAlignment = {CLAY_ALIGN_X_CENTER}, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) { CLAY_TEXT(CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE })); CLAY(CLAY_ID("RendererSpacerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 32) } } }) {} if (ACTIVE_RENDERER_INDEX == 0) { RendererButtonActive(CLAY_STRING("HTML Renderer")); RendererButtonInactive(CLAY_STRING("Canvas Renderer"), 1); } else { RendererButtonInactive(CLAY_STRING("HTML Renderer"), 0); RendererButtonActive(CLAY_STRING("Canvas Renderer")); } } } } } void RendererPageMobile() { CLAY(CLAY_ID("RendererMobile"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 16, 32, 32}, .childGap = 32 }, .backgroundColor = COLOR_LIGHT }) { CLAY(CLAY_ID("RendererLeftText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED })); CLAY(CLAY_ID("RendererSpacerLeft"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {} CLAY_TEXT(CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); CLAY_TEXT(CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); CLAY_TEXT(CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); } CLAY(CLAY_ID("RendererRightText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) { CLAY_TEXT(CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE })); CLAY(CLAY_ID("RendererSpacerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 32) }} }) {} if (ACTIVE_RENDERER_INDEX == 0) { RendererButtonActive(CLAY_STRING("HTML Renderer")); RendererButtonInactive(CLAY_STRING("Canvas Renderer"), 1); } else { RendererButtonInactive(CLAY_STRING("HTML Renderer"), 0); RendererButtonActive(CLAY_STRING("Canvas Renderer")); } } } } void DebuggerPageDesktop() { CLAY(CLAY_ID("DebuggerDesktop"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = { 82, 82, 32, 32 }, .childGap = 64 }, .backgroundColor = COLOR_RED }) { CLAY(CLAY_ID("DebuggerLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) { CLAY_TEXT(CLAY_STRING("Integrated Debug Tools"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT })); CLAY(CLAY_ID("DebuggerSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {} CLAY_TEXT(CLAY_STRING("Clay includes built in \"Chrome Inspector\"-style debug tooling."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT })); CLAY_TEXT(CLAY_STRING("View your layout hierarchy and config in real time."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT })); CLAY(CLAY_ID("DebuggerPageSpacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(32) } } }) {} CLAY_TEXT(CLAY_STRING("Press the \"d\" key to try it out now!"), CLAY_TEXT_CONFIG({ .fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE })); } CLAY(CLAY_ID("DebuggerRightImageOuter"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) { CLAY(CLAY_ID("DebuggerPageRightImageInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 558) } }, .aspectRatio = { 1620.0 / 1474.0 }, .image = {.imageData = FrameAllocateString(CLAY_STRING("/clay/images/debugger.png")) } }) {} } } } typedef struct { Clay_Vector2 clickOrigin; Clay_Vector2 positionOrigin; bool mouseDown; } ScrollbarData; ScrollbarData scrollbarData = (ScrollbarData) {}; float animationLerpValue = -1.0f; Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) { Clay_BeginLayout(); CLAY(CLAY_ID("OuterContainer"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) { CLAY(CLAY_ID("Header"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(50) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .childGap = 16, .padding = { 32, 32 } } }) { CLAY_TEXT(CLAY_STRING("Clay"), &headerTextConfig); CLAY(CLAY_ID("Spacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} if (!mobileScreen) { CLAY(CLAY_ID("LinkExamplesOuter"), { .layout = { .padding = {8, 8} } }) { CLAY_TEXT(CLAY_STRING("Examples"), CLAY_TEXT_CONFIG({ .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples") }), .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); } CLAY(CLAY_ID("LinkDocsOuter"), { .layout = { .padding = {8, 8} } }) { CLAY_TEXT(CLAY_STRING("Docs"), CLAY_TEXT_CONFIG({ .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/blob/main/README.md") }), .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }) ); } } CLAY_AUTO_ID({ .layout = { .padding = {16, 16, 6, 6} }, .backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT, .border = { .width = {2, 2, 2, 2}, .color = COLOR_RED }, .cornerRadius = CLAY_CORNER_RADIUS(10), .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://discord.gg/b4FTWkxdvT") }), }) { CLAY_TEXT(CLAY_STRING("Discord"), CLAY_TEXT_CONFIG({ .userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true }), .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); } CLAY_AUTO_ID({ .layout = { .padding = {16, 16, 6, 6} }, .backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT, .border = { .width = {2, 2, 2, 2}, .color = COLOR_RED }, .cornerRadius = CLAY_CORNER_RADIUS(10), .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay") }), }) { CLAY_TEXT(CLAY_STRING("Github"), CLAY_TEXT_CONFIG({ .userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true }), .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); } } Clay_LayoutConfig topBorderConfig = (Clay_LayoutConfig) { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(4) }}; CLAY(CLAY_ID("TopBorder1"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_5 }) {} CLAY(CLAY_ID("TopBorder2"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_4 }) {} CLAY(CLAY_ID("TopBorder3"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_3 }) {} CLAY(CLAY_ID("TopBorder4"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_2 }) {} CLAY(CLAY_ID("TopBorder5"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_1 }) {} CLAY(CLAY_ID("OuterScrollContainer"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED } }) { if (mobileScreen) { LandingPageMobile(); FeatureBlocksMobile(); DeclarativeSyntaxPageMobile(); HighPerformancePageMobile(lerpValue); RendererPageMobile(); } else { LandingPageDesktop(); FeatureBlocksDesktop(); DeclarativeSyntaxPageDesktop(); HighPerformancePageDesktop(lerpValue); RendererPageDesktop(); DebuggerPageDesktop(); } } } if (!mobileScreen && ACTIVE_RENDERER_INDEX == 1) { Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer"))); Clay_Color scrollbarColor = (Clay_Color){225, 138, 50, 120}; if (scrollbarData.mouseDown) { scrollbarColor = (Clay_Color){225, 138, 50, 200}; } else if (Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) { scrollbarColor = (Clay_Color){225, 138, 50, 160}; } float scrollHeight = scrollData.scrollContainerDimensions.height - 12; CLAY(CLAY_ID("ScrollBar"), { .floating = { .offset = { .x = -6, .y = -(scrollData.scrollPosition->y / scrollData.contentDimensions.height) * scrollHeight + 6}, .zIndex = 1, .parentId = Clay_GetElementId(CLAY_STRING("OuterScrollContainer")).id, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP }, .attachTo = CLAY_ATTACH_TO_PARENT }, .layout = { .sizing = {CLAY_SIZING_FIXED(10), CLAY_SIZING_FIXED((scrollHeight / scrollData.contentDimensions.height) * scrollHeight)} }, .backgroundColor = scrollbarColor, .cornerRadius = CLAY_CORNER_RADIUS(5) }) {} } return Clay_EndLayout(); } bool debugModeEnabled = false; CLAY_WASM_EXPORT("SetScratchMemory") void SetScratchMemory(void * memory) { frameArena.memory = memory; } CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(float width, float height, float mouseWheelX, float mouseWheelY, float mousePositionX, float mousePositionY, bool isTouchDown, bool isMouseDown, bool arrowKeyDownPressedThisFrame, bool arrowKeyUpPressedThisFrame, bool dKeyPressedThisFrame, float deltaTime) { frameArena.offset = 0; windowWidth = width; windowHeight = height; Clay_SetLayoutDimensions((Clay_Dimensions) { width, height }); Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer"))); Clay_LayoutElementHashMapItem *perfPage = Clay__GetHashMapItem(Clay_GetElementId(CLAY_STRING("PerformanceOuter")).id); // NaN propagation can cause pain here float perfPageYOffset = perfPage->boundingBox.y + scrollContainerData.scrollPosition->y; if (deltaTime == deltaTime && (ACTIVE_RENDERER_INDEX == 1 || (perfPageYOffset < height && perfPageYOffset + perfPage->boundingBox.height > 0))) { animationLerpValue += deltaTime; if (animationLerpValue > 1) { animationLerpValue -= 2; } } if (dKeyPressedThisFrame) { debugModeEnabled = !debugModeEnabled; Clay_SetDebugModeEnabled(debugModeEnabled); } Clay_SetCullingEnabled(ACTIVE_RENDERER_INDEX == 1); Clay_SetExternalScrollHandlingEnabled(ACTIVE_RENDERER_INDEX == 0); Clay__debugViewHighlightColor = (Clay_Color) {105,210,231, 120}; Clay_SetPointerState((Clay_Vector2) {mousePositionX, mousePositionY}, isMouseDown || isTouchDown); if (!isMouseDown) { scrollbarData.mouseDown = false; } if (isMouseDown && !scrollbarData.mouseDown && Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) { scrollbarData.clickOrigin = (Clay_Vector2) { mousePositionX, mousePositionY }; scrollbarData.positionOrigin = *scrollContainerData.scrollPosition; scrollbarData.mouseDown = true; } else if (scrollbarData.mouseDown) { if (scrollContainerData.contentDimensions.height > 0) { Clay_Vector2 ratio = (Clay_Vector2) { scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width, scrollContainerData.contentDimensions.height / scrollContainerData.scrollContainerDimensions.height, }; if (scrollContainerData.config.vertical) { scrollContainerData.scrollPosition->y = scrollbarData.positionOrigin.y + (scrollbarData.clickOrigin.y - mousePositionY) * ratio.y; } if (scrollContainerData.config.horizontal) { scrollContainerData.scrollPosition->x = scrollbarData.positionOrigin.x + (scrollbarData.clickOrigin.x - mousePositionX) * ratio.x; } } } if (arrowKeyDownPressedThisFrame) { if (scrollContainerData.contentDimensions.height > 0) { scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y - 50; } } else if (arrowKeyUpPressedThisFrame) { if (scrollContainerData.contentDimensions.height > 0) { scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y + 50; } } Clay_UpdateScrollContainers(isTouchDown, (Clay_Vector2) {mouseWheelX, mouseWheelY}, deltaTime); bool isMobileScreen = windowWidth < 750; if (debugModeEnabled) { isMobileScreen = windowWidth < 950; } return CreateLayout(isMobileScreen, animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue)); //---------------------------------------------------------------------------------- } // Dummy main() to please cmake - TODO get wasm working with cmake on this example int main() { return 0; } ================================================ FILE: examples/cpp-project-example/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(clay_examples_cpp_project_example CXX) set(CMAKE_CXX_STANDARD 20) if(NOT MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -g") endif() add_executable(clay_examples_cpp_project_example main.cpp) target_include_directories(clay_examples_cpp_project_example PUBLIC .) if(NOT MSVC) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") endif() ================================================ FILE: examples/cpp-project-example/main.cpp ================================================ #include #define CLAY_IMPLEMENTATION #include "../../clay.h" Clay_LayoutConfig layoutElement = Clay_LayoutConfig { .padding = {5} }; void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); } int main(void) { uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, (char *)malloc(totalMemorySize)); Clay_Initialize(clayMemory, Clay_Dimensions {1024,768}, Clay_ErrorHandler { HandleClayErrors }); Clay_BeginLayout(); CLAY_AUTO_ID({ .layout = layoutElement, .backgroundColor = {255,255,255,0} }) { CLAY_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 })); } Clay_EndLayout(); return 0; } ================================================ FILE: examples/introducing-clay-video-demo/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(clay_examples_introducing_clay_video_demo C) set(CMAKE_C_STANDARD 99) # Adding Raylib include(FetchContent) set(FETCHCONTENT_QUIET FALSE) set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games FetchContent_Declare( raylib GIT_REPOSITORY "https://github.com/raysan5/raylib.git" GIT_TAG "5.5" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(raylib) add_executable(clay_examples_introducing_clay_video_demo main.c) target_compile_options(clay_examples_introducing_clay_video_demo PUBLIC) target_include_directories(clay_examples_introducing_clay_video_demo PUBLIC .) target_link_libraries(clay_examples_introducing_clay_video_demo PUBLIC raylib) if(MSVC) set(CMAKE_C_FLAGS_DEBUG "/D CLAY_DEBUG") else() set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") endif() add_custom_command( TARGET clay_examples_introducing_clay_video_demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources) ================================================ FILE: examples/introducing-clay-video-demo/main.c ================================================ #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "../../renderers/raylib/clay_renderer_raylib.c" #include "../shared-layouts/clay-video-demo.c" // This function is new since the video was published void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); } int main(void) { Clay_Raylib_Initialize(1024, 768, "Introducing Clay Demo", FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT); // Extra parameters to this function are new since the video was published uint64_t clayRequiredMemory = Clay_MinMemorySize(); Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory)); Clay_Initialize(clayMemory, (Clay_Dimensions) { .width = GetScreenWidth(), .height = GetScreenHeight() }, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published Font fonts[1]; fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400); SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR); Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts); ClayVideoDemo_Data data = ClayVideoDemo_Initialize(); while (!WindowShouldClose()) { // Run once per frame Clay_SetLayoutDimensions((Clay_Dimensions) { .width = GetScreenWidth(), .height = GetScreenHeight() }); Vector2 mousePosition = GetMousePosition(); Vector2 scrollDelta = GetMouseWheelMoveV(); Clay_SetPointerState( (Clay_Vector2) { mousePosition.x, mousePosition.y }, IsMouseButtonDown(0) ); Clay_UpdateScrollContainers( true, (Clay_Vector2) { scrollDelta.x, scrollDelta.y }, GetFrameTime() ); Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&data); BeginDrawing(); ClearBackground(BLACK); Clay_Raylib_Render(renderCommands, fonts); EndDrawing(); } // This function is new since the video was published Clay_Raylib_Close(); } ================================================ FILE: examples/playdate-project-example/.gitignore ================================================ clay_playdate_example.pdx Source/pdex.dylib Source/pdex.elf ================================================ FILE: examples/playdate-project-example/CmakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) set(CMAKE_C_STANDARD 99) set(ENVSDK $ENV{PLAYDATE_SDK_PATH}) if (NOT ${ENVSDK} STREQUAL "") # Convert path from Windows file(TO_CMAKE_PATH ${ENVSDK} SDK) else() execute_process( COMMAND bash -c "egrep '^\\s*SDKRoot' $HOME/.Playdate/config" COMMAND head -n 1 COMMAND cut -c9- OUTPUT_VARIABLE SDK OUTPUT_STRIP_TRAILING_WHITESPACE ) endif() if (NOT EXISTS ${SDK}) message(FATAL_ERROR "SDK Path not found; set ENV value PLAYDATE_SDK_PATH") return() endif() set(CMAKE_CONFIGURATION_TYPES "Debug;Release") set(CMAKE_XCODE_GENERATE_SCHEME TRUE) # Game Name Customization set(PLAYDATE_GAME_NAME clay_playdate_example) set(PLAYDATE_GAME_DEVICE clay_playdate_example_DEVICE) project(${PLAYDATE_GAME_NAME} C ASM) if (TOOLCHAIN STREQUAL "armgcc") add_executable(${PLAYDATE_GAME_DEVICE} main.c) else() add_library(${PLAYDATE_GAME_NAME} SHARED main.c) endif() include(${SDK}/C_API/buildsupport/playdate_game.cmake) ================================================ FILE: examples/playdate-project-example/README.md ================================================ # Playdate console example This example uses a modified version of the document viewer application from the Clay video demo. The Playdate console has a very small black and white screen, so some of the fixed sizes and styles needed to be modified to make the application usable on the console. The selected document can be changed using up/down on the D-pad, and the selected document can be scrolled with the crank. ## Building You need to have the (Playdate SDK)[https://play.date/dev/] installed to be able to build this example. Once it's installed you can build it by adding -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON when initialising a directory with cmake. e.g. ``` cmake -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON cmake-build-debug ``` And then build it: ``` cmake --build cmake-build-debug ``` The pdx file will be located at examples/playdate-project-example/clay_playdate_example.pdx. You can then open it with the Playdate simulator. ## Building for the playdate device By default building this example will produce a pdx which can only run on the Playdate simulator application. To build a pdx that can run on the Playdate hardware you need to set the toolchain to use armgcc, toolchain file to the arm.cmake provided in the playdate SDK and make sure to disable the other examples. The Playdate hardware requires threads to be disabled which is not compatible with some of the other examples. e.g. To setup the cmake-build-release directory for device builds: ``` cmake -DTOOLCHAIN=armgcc -DCMAKE_TOOLCHAIN_FILE=/Users/mattahj/Developer/PlaydateSDK/C_API/buildsupport/arm.cmake -DCLAY_INCLUDE_ALL_EXAMPLES=OFF -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON -B cmake-build-release ``` And then build it: ``` cmake --build cmake-build-release ``` ================================================ FILE: examples/playdate-project-example/Source/pdxinfo ================================================ name=Clay Playdate Example author=Matthew Jennings description=A small demo of Clay running on the Playdate bundleID=dev.mattahj.clay_example imagePath= ================================================ FILE: examples/playdate-project-example/clay-video-demo-playdate.c ================================================ // This is the video demo with some adjustments so it works on the playdate // console The playdate screen is only 400x240 pixels and it can only display // black and white, so some fixed sizes and colours needed tweaking! The file // menu was also removed as it does not really make sense when there is no // pointer // // Note: The playdate console also does not support dynamic font sizes - fonts must be // created at a specific size with the pdc tool - so any font size set in the clay layout // will have no effect. #include "pd_api.h" #include "../../clay.h" #include const int FONT_ID_BODY = 0; const int FONT_ID_BUTTON = 1; Clay_Color COLOR_WHITE = { 255, 255, 255, 255 }; Clay_Color COLOR_BLACK = { 0, 0, 0, 255 }; void RenderHeaderButton(Clay_String text) { CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 4, 4 } }, .backgroundColor = COLOR_BLACK, .cornerRadius = CLAY_CORNER_RADIUS(4) }) { CLAY_TEXT( text, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BUTTON, .textColor = COLOR_WHITE }) ); } } typedef struct { Clay_String title; Clay_String contents; LCDBitmap* image; } Document; typedef struct { Document *documents; uint32_t length; } DocumentArray; #define MAX_DOCUMENTS 3 Document documentsRaw[MAX_DOCUMENTS]; DocumentArray documents = { .length = MAX_DOCUMENTS, .documents = documentsRaw }; void ClayVideoDemoPlaydate_Initialize(PlaydateAPI* pd) { documents.documents[0] = (Document){ .title = CLAY_STRING("Squirrels"), .image = pd->graphics->loadBitmap("star.png", NULL), .contents = CLAY_STRING( "The Secret Life of Squirrels: Nature's Clever Acrobats\n" "Squirrels are often overlooked creatures, dismissed as mere park " "inhabitants or backyard nuisances. Yet, beneath their fluffy tails " "and twitching noses lies an intricate world of cunning, agility, " "and survival tactics that are nothing short of fascinating. As one " "of the most common mammals in North America, squirrels have adapted " "to a wide range of environments from bustling urban centers to " "tranquil forests and have developed a variety of unique behaviors " "that continue to intrigue scientists and nature enthusiasts alike.\n" "\n" "Master Tree Climbers\n" "At the heart of a squirrel's skill set is its impressive ability to " "navigate trees with ease. Whether they're darting from branch to " "branch or leaping across wide gaps, squirrels possess an innate " "talent for acrobatics. Their powerful hind legs, which are longer " "than their front legs, give them remarkable jumping power. With a " "tail that acts as a counterbalance, squirrels can leap distances of " "up to ten times the length of their body, making them some of the " "best aerial acrobats in the animal kingdom.\n" "But it's not just their agility that makes them exceptional " "climbers. Squirrels' sharp, curved claws allow them to grip tree " "bark with precision, while the soft pads on their feet provide " "traction on slippery surfaces. Their ability to run at high speeds " "and scale vertical trunks with ease is a testament to the " "evolutionary adaptations that have made them so successful in their " "arboreal habitats.\n" "\n" "Food Hoarders Extraordinaire\n" "Squirrels are often seen frantically gathering nuts, seeds, and " "even fungi in preparation for winter. While this behavior may seem " "like instinctual hoarding, it is actually a survival strategy that " "has been honed over millions of years. Known as \"scatter " "hoarding,\" squirrels store their food in a variety of hidden " "locations, often burying it deep in the soil or stashing it in " "hollowed-out tree trunks.\n" "Interestingly, squirrels have an incredible memory for the " "locations of their caches. Research has shown that they can " "remember thousands of hiding spots, often returning to them months " "later when food is scarce. However, they don't always recover every " "stash some forgotten caches eventually sprout into new trees, " "contributing to forest regeneration. This unintentional role as " "forest gardeners highlights the ecological importance of squirrels " "in their ecosystems.\n" "\n" "The Great Squirrel Debate: Urban vs. Wild\n" "While squirrels are most commonly associated with rural or wooded " "areas, their adaptability has allowed them to thrive in urban " "environments as well. In cities, squirrels have become adept at " "finding food sources in places like parks, streets, and even " "garbage cans. However, their urban counterparts face unique " "challenges, including traffic, predators, and the lack of natural " "shelters. Despite these obstacles, squirrels in urban areas are " "often observed using human infrastructure such as buildings, " "bridges, and power lines as highways for their acrobatic " "escapades.\n" "There is, however, a growing concern regarding the impact of urban " "life on squirrel populations. Pollution, deforestation, and the " "loss of natural habitats are making it more difficult for squirrels " "to find adequate food and shelter. As a result, conservationists " "are focusing on creating squirrel-friendly spaces within cities, " "with the goal of ensuring these resourceful creatures continue to " "thrive in both rural and urban landscapes.\n" "\n" "A Symbol of Resilience\n" "In many cultures, squirrels are symbols of resourcefulness, " "adaptability, and preparation. Their ability to thrive in a variety " "of environments while navigating challenges with agility and grace " "serves as a reminder of the resilience inherent in nature. Whether " "you encounter them in a quiet forest, a city park, or your own " "backyard, squirrels are creatures that never fail to amaze with " "their endless energy and ingenuity.\n" "In the end, squirrels may be small, but they are mighty in their " "ability to survive and thrive in a world that is constantly " "changing. So next time you spot one hopping across a branch or " "darting across your lawn, take a moment to appreciate the " "remarkable acrobat at work a true marvel of the natural world.\n" ) }; documents.documents[1] = (Document){ .title = CLAY_STRING("Lorem Ipsum"), .image = pd->graphics->loadBitmap("star.png", NULL), .contents = CLAY_STRING( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim " "ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " "aliquip ex ea commodo consequat. Duis aute irure dolor in " "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " "culpa qui officia deserunt mollit anim id est laborum." ) }; documents.documents[2] = (Document){ .title = CLAY_STRING("Vacuum Instructions"), .image = pd->graphics->loadBitmap("star.png", NULL), .contents = CLAY_STRING( "Chapter 3: Getting Started - Unpacking and Setup\n" "\n" "Congratulations on your new SuperClean Pro 5000 vacuum cleaner! In " "this section, we will guide you through the simple steps to get " "your vacuum up and running. Before you begin, please ensure that " "you have all the components listed in the \"Package Contents\" " "section on page 2.\n" "\n" "1. Unboxing Your Vacuum\n" "Carefully remove the vacuum cleaner from the box. Avoid using sharp " "objects that could damage the product. Once removed, place the unit " "on a flat, stable surface to proceed with the setup. Inside the " "box, you should find:\n" "\n" " The main vacuum unit\n" " A telescoping extension wand\n" " A set of specialized cleaning tools (crevice tool, upholstery " "brush, etc.)\n" " A reusable dust bag (if applicable)\n" " A power cord with a 3-prong plug\n" " A set of quick-start instructions\n" "\n" "2. Assembling Your Vacuum\n" "Begin by attaching the extension wand to the main body of the " "vacuum cleaner. Line up the connectors and twist the wand into " "place until you hear a click. Next, select the desired cleaning " "tool and firmly attach it to the wand's end, ensuring it is " "securely locked in.\n" "\n" "For models that require a dust bag, slide the bag into the " "compartment at the back of the vacuum, making sure it is properly " "aligned with the internal mechanism. If your vacuum uses a bagless " "system, ensure the dust container is correctly seated and locked in " "place before use.\n" "\n" "3. Powering On\n" "To start the vacuum, plug the power cord into a grounded electrical " "outlet. Once plugged in, locate the power switch, usually " "positioned on the side of the handle or body of the unit, depending " "on your model. Press the switch to the \"On\" position, and you " "should hear the motor begin to hum. If the vacuum does not power " "on, check that the power cord is securely plugged in, and ensure " "there are no blockages in the power switch.\n" "\n" "Note: Before first use, ensure that the vacuum filter (if your " "model has one) is properly installed. If unsure, refer to \"Section " "5: Maintenance\" for filter installation instructions." ) }; } Clay_RenderCommandArray ClayVideoDemoPlaydate_CreateLayout(int selectedDocumentIndex) { Clay_BeginLayout(); Clay_Sizing layoutExpand = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }; Clay_BorderElementConfig contentBorders = { .color = COLOR_BLACK, .width = { .top = 1, .left = 1, .right = 1, .bottom = 1 } }; // Build UI here CLAY(CLAY_ID("OuterContainer"), { .backgroundColor = COLOR_WHITE, .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = layoutExpand, .padding = CLAY_PADDING_ALL(8), .childGap = 4 } }) { // Child elements go inside braces CLAY(CLAY_ID("HeaderBar"), { .layout = { .sizing = { .height = CLAY_SIZING_FIXED(30), .width = CLAY_SIZING_GROW(0) }, .childGap = 8, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, }) { // Header buttons go here CLAY(CLAY_ID("FileButton"), { .layout = { .padding = { 8, 8, 4, 4 } }, .backgroundColor = COLOR_BLACK, .cornerRadius = CLAY_CORNER_RADIUS(4) }) { CLAY_TEXT( CLAY_STRING("File"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BUTTON, .textColor = COLOR_WHITE }) ); } RenderHeaderButton(CLAY_STRING("Edit")); CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0) } } }) {} RenderHeaderButton(CLAY_STRING("Upload")); RenderHeaderButton(CLAY_STRING("Media")); RenderHeaderButton(CLAY_STRING("Support")); } CLAY(CLAY_ID("LowerContent"), { .layout = { .sizing = layoutExpand, .childGap = 8 }, }) { CLAY(CLAY_ID("Sidebar"), { .border = contentBorders, .cornerRadius = CLAY_CORNER_RADIUS(4), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = CLAY_PADDING_ALL(8), .childGap = 4, .sizing = { .width = CLAY_SIZING_FIXED(125), .height = CLAY_SIZING_GROW(0) } } }) { for (int i = 0; i < documents.length; i++) { Document document = documents.documents[i]; Clay_LayoutConfig sidebarButtonLayout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(8) }; if (i == selectedDocumentIndex) { CLAY_AUTO_ID({ .layout = sidebarButtonLayout, .backgroundColor = COLOR_BLACK, .cornerRadius = CLAY_CORNER_RADIUS(4) }) { CLAY_TEXT( document.title, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BUTTON, .textColor = COLOR_WHITE }) ); } } else { CLAY_AUTO_ID({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color){ 0, 0, 0, Clay_Hovered() ? 120 : 0 }, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = contentBorders }) { CLAY_TEXT( document.title, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BUTTON, .textColor = COLOR_BLACK, }) ); } } } } CLAY(CLAY_ID("MainContent"), { .border = contentBorders, .cornerRadius = CLAY_CORNER_RADIUS(4), .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8, .padding = CLAY_PADDING_ALL(8), .sizing = layoutExpand } }) { Document selectedDocument = documents.documents[selectedDocumentIndex]; CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT, .childGap = 4, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_BOTTOM } } }) { CLAY_TEXT( selectedDocument.title, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY, .textColor = COLOR_BLACK }) ); CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_FIXED(32), .height = CLAY_SIZING_FIXED(30) } }, .image = { .imageData = selectedDocument.image, .sourceDimensions = { 32, 30 } } }) {} } CLAY_TEXT( selectedDocument.contents, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY, .textColor = COLOR_BLACK }) ); } } } Clay_RenderCommandArray renderCommands = Clay_EndLayout(); for (int32_t i = 0; i < renderCommands.length; i++) { Clay_RenderCommandArray_Get(&renderCommands, i); } return renderCommands; } ================================================ FILE: examples/playdate-project-example/main.c ================================================ #include "pd_api.h" #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "../../renderers/playdate/clay_renderer_playdate.c" #include "clay-video-demo-playdate.c" static int update(void *userdata); #define NUM_FONTS 2 const char *fontsToLoad[NUM_FONTS] = { "/System/Fonts/Asheville-Sans-14-Bold.pft", "/System/Fonts/Roobert-10-Bold.pft" }; void HandleClayErrors(Clay_ErrorData errorData) {} struct TextUserData { LCDFont *font[NUM_FONTS]; PlaydateAPI *pd; }; static struct TextUserData textUserData = { .font = { NULL }, .pd = NULL }; static Clay_Dimensions PlayDate_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { struct TextUserData *textUserData = userData; int width = textUserData->pd->graphics->getTextWidth( textUserData->font[config->fontId], text.chars, Clay_Playdate_CountUtf8Codepoints(text.chars, text.length), kUTF8Encoding, 0 ); int height = textUserData->pd->graphics->getFontHeight(textUserData->font[config->fontId]); return (Clay_Dimensions){ .width = (float)width, .height = (float)height, }; } #ifdef _WINDLL __declspec(dllexport) #endif int eventHandler(PlaydateAPI* pd, PDSystemEvent event, uint32_t eventArg) { if (event == kEventInit) { const char *err; for (int i = 0; i < NUM_FONTS; ++i) { textUserData.font[i] = pd->graphics->loadFont(fontsToLoad[i], &err); if (textUserData.font[i] == NULL) { pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontsToLoad[i], err); } } textUserData.pd = pd; pd->system->setUpdateCallback(update, pd); uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory( totalMemorySize, pd->system->realloc(NULL, totalMemorySize) ); Clay_Initialize( clayMemory, (Clay_Dimensions){ (float)pd->display->getWidth(), (float)pd->display->getHeight() }, (Clay_ErrorHandler){HandleClayErrors} ); Clay_SetMeasureTextFunction(PlayDate_MeasureText, &textUserData); ClayVideoDemoPlaydate_Initialize(pd); } return 0; } int selectedDocumentIndex = 0; #define WRAP_RANGE(x, N) ((((x) % (N)) + (N)) % (N)) static int update(void *userdata) { PlaydateAPI *pd = userdata; PDButtons pushedButtons; pd->system->getButtonState(NULL, &pushedButtons, NULL); if (pushedButtons & kButtonDown) { selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex + 1, MAX_DOCUMENTS); } else if (pushedButtons & kButtonUp) { selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex - 1, MAX_DOCUMENTS); } pd->graphics->clear(kColorWhite); // A bit hacky, setting the cursor on to the document view so it can be // scrolled.. Clay_SetPointerState( (Clay_Vector2){ .x = pd->display->getWidth() / 2.0f, .y = pd->display->getHeight() / 2.0f }, false ); float crankDelta = pd->system->getCrankChange(); Clay_UpdateScrollContainers( false, (Clay_Vector2){ 0, -crankDelta * 0.25f }, pd->system->getElapsedTime() ); Clay_RenderCommandArray renderCommands = ClayVideoDemoPlaydate_CreateLayout(selectedDocumentIndex); Clay_Playdate_Render(pd, renderCommands, textUserData.font); return 1; } ================================================ FILE: examples/raylib-multi-context/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(clay_examples_raylib_multi_context C) set(CMAKE_C_STANDARD 99) # Adding Raylib include(FetchContent) set(FETCHCONTENT_QUIET FALSE) set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games FetchContent_Declare( raylib GIT_REPOSITORY "https://github.com/raysan5/raylib.git" GIT_TAG "5.5" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(raylib) add_executable(clay_examples_raylib_multi_context main.c) target_compile_options(clay_examples_raylib_multi_context PUBLIC) target_include_directories(clay_examples_raylib_multi_context PUBLIC .) target_link_libraries(clay_examples_raylib_multi_context PUBLIC raylib) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") add_custom_command( TARGET clay_examples_raylib_multi_context POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources) ================================================ FILE: examples/raylib-multi-context/main.c ================================================ #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "../../renderers/raylib/clay_renderer_raylib.c" #include "../shared-layouts/clay-video-demo.c" void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); } Clay_RenderCommandArray CreateLayout(Clay_Context* context, ClayVideoDemo_Data *data) { Clay_SetCurrentContext(context); Clay_SetDebugModeEnabled(true); // Run once per frame Clay_SetLayoutDimensions((Clay_Dimensions) { .width = GetScreenWidth(), .height = GetScreenHeight() / 2 }); Vector2 mousePosition = GetMousePosition(); mousePosition.y -= data->yOffset; Vector2 scrollDelta = GetMouseWheelMoveV(); Clay_SetPointerState( (Clay_Vector2) { mousePosition.x, mousePosition.y }, IsMouseButtonDown(0) ); Clay_UpdateScrollContainers( true, (Clay_Vector2) { scrollDelta.x, scrollDelta.y }, GetFrameTime() ); return ClayVideoDemo_CreateLayout(data); } int main(void) { documents.documents = (Document[]) { { .title = CLAY_STRING("Squirrels"), .contents = CLAY_STRING("The Secret Life of Squirrels: Nature's Clever Acrobats\n""Squirrels are often overlooked creatures, dismissed as mere park inhabitants or backyard nuisances. Yet, beneath their fluffy tails and twitching noses lies an intricate world of cunning, agility, and survival tactics that are nothing short of fascinating. As one of the most common mammals in North America, squirrels have adapted to a wide range of environments from bustling urban centers to tranquil forests and have developed a variety of unique behaviors that continue to intrigue scientists and nature enthusiasts alike.\n""\n""Master Tree Climbers\n""At the heart of a squirrel's skill set is its impressive ability to navigate trees with ease. Whether they're darting from branch to branch or leaping across wide gaps, squirrels possess an innate talent for acrobatics. Their powerful hind legs, which are longer than their front legs, give them remarkable jumping power. With a tail that acts as a counterbalance, squirrels can leap distances of up to ten times the length of their body, making them some of the best aerial acrobats in the animal kingdom.\n""But it's not just their agility that makes them exceptional climbers. Squirrels' sharp, curved claws allow them to grip tree bark with precision, while the soft pads on their feet provide traction on slippery surfaces. Their ability to run at high speeds and scale vertical trunks with ease is a testament to the evolutionary adaptations that have made them so successful in their arboreal habitats.\n""\n""Food Hoarders Extraordinaire\n""Squirrels are often seen frantically gathering nuts, seeds, and even fungi in preparation for winter. While this behavior may seem like instinctual hoarding, it is actually a survival strategy that has been honed over millions of years. Known as \"scatter hoarding,\" squirrels store their food in a variety of hidden locations, often burying it deep in the soil or stashing it in hollowed-out tree trunks.\n""Interestingly, squirrels have an incredible memory for the locations of their caches. Research has shown that they can remember thousands of hiding spots, often returning to them months later when food is scarce. However, they don't always recover every stash some forgotten caches eventually sprout into new trees, contributing to forest regeneration. This unintentional role as forest gardeners highlights the ecological importance of squirrels in their ecosystems.\n""\n""The Great Squirrel Debate: Urban vs. Wild\n""While squirrels are most commonly associated with rural or wooded areas, their adaptability has allowed them to thrive in urban environments as well. In cities, squirrels have become adept at finding food sources in places like parks, streets, and even garbage cans. However, their urban counterparts face unique challenges, including traffic, predators, and the lack of natural shelters. Despite these obstacles, squirrels in urban areas are often observed using human infrastructure such as buildings, bridges, and power lines as highways for their acrobatic escapades.\n""There is, however, a growing concern regarding the impact of urban life on squirrel populations. Pollution, deforestation, and the loss of natural habitats are making it more difficult for squirrels to find adequate food and shelter. As a result, conservationists are focusing on creating squirrel-friendly spaces within cities, with the goal of ensuring these resourceful creatures continue to thrive in both rural and urban landscapes.\n""\n""A Symbol of Resilience\n""In many cultures, squirrels are symbols of resourcefulness, adaptability, and preparation. Their ability to thrive in a variety of environments while navigating challenges with agility and grace serves as a reminder of the resilience inherent in nature. Whether you encounter them in a quiet forest, a city park, or your own backyard, squirrels are creatures that never fail to amaze with their endless energy and ingenuity.\n""In the end, squirrels may be small, but they are mighty in their ability to survive and thrive in a world that is constantly changing. So next time you spot one hopping across a branch or darting across your lawn, take a moment to appreciate the remarkable acrobat at work a true marvel of the natural world.\n") }, { .title = CLAY_STRING("Lorem Ipsum"), .contents = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") }, { .title = CLAY_STRING("Vacuum Instructions"), .contents = CLAY_STRING("Chapter 3: Getting Started - Unpacking and Setup\n""\n""Congratulations on your new SuperClean Pro 5000 vacuum cleaner! In this section, we will guide you through the simple steps to get your vacuum up and running. Before you begin, please ensure that you have all the components listed in the \"Package Contents\" section on page 2.\n""\n""1. Unboxing Your Vacuum\n""Carefully remove the vacuum cleaner from the box. Avoid using sharp objects that could damage the product. Once removed, place the unit on a flat, stable surface to proceed with the setup. Inside the box, you should find:\n""\n"" The main vacuum unit\n"" A telescoping extension wand\n"" A set of specialized cleaning tools (crevice tool, upholstery brush, etc.)\n"" A reusable dust bag (if applicable)\n"" A power cord with a 3-prong plug\n"" A set of quick-start instructions\n""\n""2. Assembling Your Vacuum\n""Begin by attaching the extension wand to the main body of the vacuum cleaner. Line up the connectors and twist the wand into place until you hear a click. Next, select the desired cleaning tool and firmly attach it to the wand's end, ensuring it is securely locked in.\n""\n""For models that require a dust bag, slide the bag into the compartment at the back of the vacuum, making sure it is properly aligned with the internal mechanism. If your vacuum uses a bagless system, ensure the dust container is correctly seated and locked in place before use.\n""\n""3. Powering On\n""To start the vacuum, plug the power cord into a grounded electrical outlet. Once plugged in, locate the power switch, usually positioned on the side of the handle or body of the unit, depending on your model. Press the switch to the \"On\" position, and you should hear the motor begin to hum. If the vacuum does not power on, check that the power cord is securely plugged in, and ensure there are no blockages in the power switch.\n""\n""Note: Before first use, ensure that the vacuum filter (if your model has one) is properly installed. If unsure, refer to \"Section 5: Maintenance\" for filter installation instructions.") }, { .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") }, { .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") }, }; Clay_Raylib_Initialize(1024, 768, "Introducing Clay Demo", FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT); // Extra parameters to this function are new since the video was published Font fonts[1]; fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400); SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR); uint64_t clayRequiredMemory = Clay_MinMemorySize(); Clay_Arena clayMemoryTop = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory)); Clay_Context *clayContextTop = Clay_Initialize(clayMemoryTop, (Clay_Dimensions) { .width = GetScreenWidth(), .height = GetScreenHeight() / 2 }, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published ClayVideoDemo_Data dataTop = ClayVideoDemo_Initialize(); Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts); Clay_Arena clayMemoryBottom = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory)); Clay_Context *clayContextBottom = Clay_Initialize(clayMemoryBottom, (Clay_Dimensions) { .width = GetScreenWidth(), .height = GetScreenHeight() / 2 }, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published ClayVideoDemo_Data dataBottom = ClayVideoDemo_Initialize(); Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts); while (!WindowShouldClose()) { dataBottom.yOffset = GetScreenHeight() / 2; Clay_RenderCommandArray renderCommandsTop = CreateLayout(clayContextTop, &dataTop); Clay_RenderCommandArray renderCommandsBottom = CreateLayout(clayContextBottom, &dataBottom); BeginDrawing(); ClearBackground(BLACK); Clay_Raylib_Render(renderCommandsTop, fonts); Clay_Raylib_Render(renderCommandsBottom, fonts); EndDrawing(); } Clay_Raylib_Close(); } ================================================ FILE: examples/raylib-sidebar-scrolling-container/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(clay_examples_raylib_sidebar_scrolling_container C) set(CMAKE_C_STANDARD 99) # Adding Raylib include(FetchContent) set(FETCHCONTENT_QUIET FALSE) set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games FetchContent_Declare( raylib GIT_REPOSITORY "https://github.com/raysan5/raylib.git" GIT_TAG "5.5" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(raylib) add_executable(clay_examples_raylib_sidebar_scrolling_container main.c multi-compilation-unit.c) target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC) target_include_directories(clay_examples_raylib_sidebar_scrolling_container PUBLIC .) target_link_libraries(clay_examples_raylib_sidebar_scrolling_container PUBLIC raylib) if(MSVC) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") else() set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") endif() add_custom_command( TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources) ================================================ FILE: examples/raylib-sidebar-scrolling-container/main.c ================================================ #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "../../renderers/raylib/clay_renderer_raylib.c" const uint32_t FONT_ID_BODY_24 = 0; const uint32_t FONT_ID_BODY_16 = 1; #define COLOR_ORANGE (Clay_Color) {225, 138, 50, 255} #define COLOR_BLUE (Clay_Color) {111, 173, 162, 255} Texture2D profilePicture; #define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = vector.x, .y = vector.y } Clay_String profileText = CLAY_STRING_CONST("Profile Page one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen"); Clay_TextElementConfig headerTextConfig = { .fontId = 1, .letterSpacing = 5, .fontSize = 16, .textColor = {0,0,0,255} }; void HandleHeaderButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData) { if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { // Do some click handling } } Clay_ElementDeclaration HeaderButtonStyle(bool hovered) { return (Clay_ElementDeclaration) { .layout = {.padding = {16, 16, 8, 8}}, .backgroundColor = hovered ? COLOR_ORANGE : COLOR_BLUE, }; } // Examples of re-usable "Components" void RenderHeaderButton(Clay_String text) { CLAY_AUTO_ID(HeaderButtonStyle(Clay_Hovered())) { CLAY_TEXT(text, CLAY_TEXT_CONFIG(headerTextConfig)); } } Clay_LayoutConfig dropdownTextItemLayout = { .padding = {8, 8, 4, 4} }; Clay_TextElementConfig dropdownTextElementConfig = { .fontSize = 24, .textColor = {255,255,255,255} }; void RenderDropdownTextItem(int index) { CLAY_AUTO_ID({ .layout = dropdownTextItemLayout, .backgroundColor = {180, 180, 180, 255} }) { CLAY_TEXT(CLAY_STRING("I'm a text field in a scroll container."), &dropdownTextElementConfig); } } Clay_RenderCommandArray CreateLayout(void) { Clay_BeginLayout(); CLAY(CLAY_ID("OuterContainer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, .padding = { 16, 16, 16, 16 }, .childGap = 16 }, .backgroundColor = {200, 200, 200, 255} }) { CLAY(CLAY_ID("SideBar"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0) }, .padding = {16, 16, 16, 16 }, .childGap = 16 }, .backgroundColor = {150, 150, 255, 255} }) { CLAY(CLAY_ID("ProfilePictureOuter"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = { 8, 8, 8, 8 }, .childGap = 8, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = {130, 130, 255, 255} }) { CLAY(CLAY_ID("ProfilePicture"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) } }, .image = { .imageData = &profilePicture }}) {} CLAY_TEXT(profileText, CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0, 0, 0, 255}, .textAlignment = CLAY_TEXT_ALIGN_RIGHT })); } CLAY(CLAY_ID("SidebarBlob1"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) }}, .backgroundColor = {110, 110, 255, 255} }) {} CLAY(CLAY_ID("SidebarBlob2"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) }}, .backgroundColor = {110, 110, 255, 255} }) {} CLAY(CLAY_ID("SidebarBlob3"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) }}, .backgroundColor = {110, 110, 255, 255} }) {} CLAY(CLAY_ID("SidebarBlob4"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) }}, .backgroundColor = {110, 110, 255, 255} }) {} } CLAY(CLAY_ID("RightPanel"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, .childGap = 16 }}) { CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .childAlignment = { .x = CLAY_ALIGN_X_RIGHT }, .padding = {8, 8, 8, 8 }, .childGap = 8 }, .backgroundColor = {180, 180, 180, 255} }) { RenderHeaderButton(CLAY_STRING("Header Item 1")); RenderHeaderButton(CLAY_STRING("Header Item 2")); RenderHeaderButton(CLAY_STRING("Header Item 3")); } CLAY(CLAY_ID("MainContent"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {16, 16, 16, 16}, .childGap = 16, .sizing = { .width = CLAY_SIZING_GROW(0) } }, .backgroundColor = {200, 200, 255, 255}, .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, }) { CLAY(CLAY_ID("FloatingContainer"), { .layout = { .sizing = { .width = CLAY_SIZING_PERCENT(0.5), .height = CLAY_SIZING_FIXED(300) }, .padding = { 16, 16, 16, 16 }}, .backgroundColor = { 140, 80, 200, 200 }, .floating = { .attachTo = CLAY_ATTACH_TO_PARENT, .zIndex = 1, .attachPoints = { CLAY_ATTACH_POINT_CENTER_TOP, CLAY_ATTACH_POINT_CENTER_TOP }, .offset = {0, 0} }, .border = { .width = CLAY_BORDER_OUTSIDE(2), .color = {80, 80, 80, 255} }, }) { CLAY_TEXT(CLAY_STRING("I'm an inline floating container."), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255,255,255,255} })); } CLAY_TEXT(CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt."), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {0,0,0,255} })); CLAY(CLAY_ID("Photos2"), { .layout = { .childGap = 16, .padding = { 16, 16, 16, 16 }}, .backgroundColor = {180, 180, 220, Clay_Hovered() ? 120 : 255} }) { CLAY(CLAY_ID("Picture4"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture }}) {} CLAY(CLAY_ID("Picture5"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture }}) {} CLAY(CLAY_ID("Picture6"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture }}) {} } CLAY_TEXT(CLAY_STRING("Faucibus purus in massa tempor nec. Nec ullamcorper sit amet risus nullam eget felis eget nunc. Diam vulputate ut pharetra sit amet aliquam id diam. Lacus suspendisse faucibus interdum posuere lorem. A diam sollicitudin tempor id. Amet massa vitae tortor condimentum lacinia. Aliquet nibh praesent tristique magna."), CLAY_TEXT_CONFIG({ .fontSize = 24, .lineHeight = 60, .textColor = {0,0,0,255}, .textAlignment = CLAY_TEXT_ALIGN_CENTER })); CLAY_TEXT(CLAY_STRING("Suspendisse in est ante in nibh. Amet venenatis urna cursus eget nunc scelerisque viverra. Elementum sagittis vitae et leo duis ut diam quam nulla. Enim nulla aliquet porttitor lacus. Pellentesque habitant morbi tristique senectus et. Facilisi nullam vehicula ipsum a arcu cursus vitae.\nSem fringilla ut morbi tincidunt. Euismod quis viverra nibh cras pulvinar mattis nunc sed. Velit sed ullamcorper morbi tincidunt ornare massa. Varius quam quisque id diam vel quam. Nulla pellentesque dignissim enim sit amet venenatis. Enim lobortis scelerisque fermentum dui faucibus in. Pretium viverra suspendisse potenti nullam ac tortor vitae. Lectus vestibulum mattis ullamcorper velit sed. Eget mauris pharetra et ultrices neque ornare aenean euismod elementum. Habitant morbi tristique senectus et. Integer vitae justo eget magna fermentum iaculis eu. Semper quis lectus nulla at volutpat diam. Enim praesent elementum facilisis leo. Massa vitae tortor condimentum lacinia quis vel."), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); CLAY(CLAY_ID("Photos"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, .childGap = 16, .padding = {16, 16, 16, 16} }, .backgroundColor = {180, 180, 220, 255} }) { CLAY(CLAY_ID("Picture2"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120) }}, .aspectRatio = 1, .image = { .imageData = &profilePicture }}) {} CLAY(CLAY_ID("Picture1"), { .layout = { .childAlignment = { .x = CLAY_ALIGN_X_CENTER }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {8, 8, 8, 8} }, .backgroundColor = {170, 170, 220, 255} }) { CLAY(CLAY_ID("ProfilePicture2"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture }}) {} CLAY_TEXT(CLAY_STRING("Image caption below"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); } CLAY(CLAY_ID("Picture3"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120) }}, .aspectRatio = 1, .image = { .imageData = &profilePicture }}) {} } CLAY_TEXT(CLAY_STRING("Amet cursus sit amet dictum sit amet justo donec. Et malesuada fames ac turpis egestas maecenas. A lacus vestibulum sed arcu non odio euismod lacinia. Gravida neque convallis a cras. Dui nunc mattis enim ut tellus elementum sagittis vitae et. Orci sagittis eu volutpat odio facilisis mauris. Neque gravida in fermentum et sollicitudin ac orci. Ultrices dui sapien eget mi proin sed libero. Euismod quis viverra nibh cras pulvinar mattis. Diam volutpat commodo sed egestas egestas. In fermentum posuere urna nec tincidunt praesent semper. Integer eget aliquet nibh praesent tristique magna.\nId cursus metus aliquam eleifend mi in. Sed pulvinar proin gravida hendrerit lectus a. Etiam tempor orci eu lobortis elementum nibh tellus. Nullam vehicula ipsum a arcu cursus vitae. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Condimentum lacinia quis vel eros donec ac odio. Mattis pellentesque id nibh tortor id aliquet lectus. Turpis egestas integer eget aliquet nibh praesent tristique. Porttitor massa id neque aliquam vestibulum morbi. Mauris commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Nunc scelerisque viverra mauris in aliquam sem fringilla. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla.\nLacus laoreet non curabitur gravida arcu ac tortor dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Tristique senectus et netus et malesuada fames ac. Nunc aliquet bibendum enim facilisis gravida. Egestas maecenas pharetra convallis posuere morbi leo urna molestie. Sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Ac turpis egestas maecenas pharetra convallis posuere morbi leo urna. Viverra vitae congue eu consequat. Aliquet enim tortor at auctor urna. Ornare massa eget egestas purus viverra accumsan in nisl nisi. Elit pellentesque habitant morbi tristique senectus et netus et malesuada.\nSuspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Lobortis feugiat vivamus at augue eget arcu. Vitae justo eget magna fermentum iaculis eu. Gravida rutrum quisque non tellus orci. Ipsum faucibus vitae aliquet nec. Nullam non nisi est sit amet. Nunc consequat interdum varius sit amet mattis vulputate enim. Sem fringilla ut morbi tincidunt augue interdum. Vitae purus faucibus ornare suspendisse. Massa tincidunt nunc pulvinar sapien et. Fringilla ut morbi tincidunt augue interdum velit euismod in. Donec massa sapien faucibus et. Est placerat in egestas erat imperdiet. Gravida rutrum quisque non tellus. Morbi non arcu risus quis varius quam quisque id diam. Habitant morbi tristique senectus et netus et malesuada fames ac. Eget lorem dolor sed viverra.\nOrnare massa eget egestas purus viverra. Varius vel pharetra vel turpis nunc eget lorem. Consectetur purus ut faucibus pulvinar elementum. Placerat in egestas erat imperdiet sed euismod nisi. Interdum velit euismod in pellentesque massa placerat duis ultricies lacus. Aliquam nulla facilisi cras fermentum odio eu. Est pellentesque elit ullamcorper dignissim cras tincidunt. Nunc sed id semper risus in hendrerit gravida rutrum. A pellentesque sit amet porttitor eget dolor morbi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Sed id semper risus in hendrerit gravida. Tincidunt praesent semper feugiat nibh. Aliquet lectus proin nibh nisl condimentum id venenatis a. Enim sit amet venenatis urna cursus eget. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Lacinia quis vel eros donec ac odio tempor orci. Donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Erat pellentesque adipiscing commodo elit at.\nEgestas sed sed risus pretium quam vulputate. Vitae congue mauris rhoncus aenean vel elit scelerisque mauris pellentesque. Aliquam malesuada bibendum arcu vitae elementum. Congue mauris rhoncus aenean vel elit scelerisque mauris. Pellentesque dignissim enim sit amet venenatis urna cursus. Et malesuada fames ac turpis egestas sed tempus urna. Vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Nibh cras pulvinar mattis nunc sed blandit libero. Fringilla est ullamcorper eget nulla facilisi etiam dignissim. Aenean euismod elementum nisi quis eleifend quam adipiscing vitae proin. Mauris pharetra et ultrices neque ornare aenean euismod elementum. Ornare quam viverra orci sagittis eu. Odio ut sem nulla pharetra diam sit amet nisl suscipit. Ornare lectus sit amet est. Ullamcorper sit amet risus nullam eget. Tincidunt lobortis feugiat vivamus at augue eget arcu dictum.\nUrna nec tincidunt praesent semper feugiat nibh. Ut venenatis tellus in metus vulputate eu scelerisque felis. Cursus risus at ultrices mi tempus. In pellentesque massa placerat duis ultricies lacus sed turpis. Platea dictumst quisque sagittis purus. Cras adipiscing enim eu turpis egestas. Egestas sed tempus urna et pharetra pharetra. Netus et malesuada fames ac turpis egestas integer eget aliquet. Ac turpis egestas sed tempus. Sed lectus vestibulum mattis ullamcorper velit sed. Ante metus dictum at tempor commodo ullamcorper a. Augue neque gravida in fermentum et sollicitudin ac. Praesent semper feugiat nibh sed pulvinar proin gravida. Metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices. Neque gravida in fermentum et sollicitudin ac orci phasellus egestas.\nRidiculus mus mauris vitae ultricies. Morbi quis commodo odio aenean. Duis ultricies lacus sed turpis. Non pulvinar neque laoreet suspendisse interdum consectetur. Scelerisque eleifend donec pretium vulputate sapien nec sagittis aliquam. Volutpat est velit egestas dui id ornare arcu odio ut. Viverra tellus in hac habitasse platea dictumst vestibulum rhoncus est. Vestibulum lectus mauris ultrices eros. Sed blandit libero volutpat sed cras ornare. Id leo in vitae turpis massa sed elementum tempus. Gravida dictum fusce ut placerat orci nulla pellentesque. Pretium quam vulputate dignissim suspendisse in. Nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Risus viverra adipiscing at in tellus. Turpis nunc eget lorem dolor sed viverra ipsum. Senectus et netus et malesuada fames ac. Habitasse platea dictumst vestibulum rhoncus est. Nunc sed id semper risus in hendrerit gravida. Felis eget velit aliquet sagittis id. Eget felis eget nunc lobortis.\nMaecenas pharetra convallis posuere morbi leo. Maecenas volutpat blandit aliquam etiam. A condimentum vitae sapien pellentesque habitant morbi tristique senectus et. Pulvinar mattis nunc sed blandit libero volutpat sed. Feugiat in ante metus dictum at tempor commodo ullamcorper. Vel pharetra vel turpis nunc eget lorem dolor. Est placerat in egestas erat imperdiet sed euismod. Quisque non tellus orci ac auctor augue mauris augue. Placerat vestibulum lectus mauris ultrices eros in cursus turpis. Enim nunc faucibus a pellentesque sit. Adipiscing vitae proin sagittis nisl. Iaculis at erat pellentesque adipiscing commodo elit at imperdiet. Aliquam sem fringilla ut morbi.\nArcu odio ut sem nulla pharetra diam sit amet nisl. Non diam phasellus vestibulum lorem sed. At erat pellentesque adipiscing commodo elit at. Lacus luctus accumsan tortor posuere ac ut consequat. Et malesuada fames ac turpis egestas integer. Tristique magna sit amet purus. A condimentum vitae sapien pellentesque habitant. Quis varius quam quisque id diam vel quam. Est ullamcorper eget nulla facilisi etiam dignissim diam quis. Augue interdum velit euismod in pellentesque massa. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant. Vulputate eu scelerisque felis imperdiet. Nibh tellus molestie nunc non blandit massa. Velit euismod in pellentesque massa placerat. Sed cras ornare arcu dui. Ut sem viverra aliquet eget sit. Eu lobortis elementum nibh tellus molestie nunc non. Blandit libero volutpat sed cras ornare arcu dui vivamus.\nSit amet aliquam id diam maecenas. Amet risus nullam eget felis eget nunc lobortis mattis aliquam. Magna sit amet purus gravida. Egestas purus viverra accumsan in nisl nisi. Leo duis ut diam quam. Ante metus dictum at tempor commodo ullamcorper. Ac turpis egestas integer eget. Fames ac turpis egestas integer eget aliquet nibh. Sem integer vitae justo eget magna fermentum. Semper auctor neque vitae tempus quam pellentesque nec nam aliquam. Vestibulum mattis ullamcorper velit sed. Consectetur adipiscing elit duis tristique sollicitudin nibh. Massa id neque aliquam vestibulum morbi blandit cursus risus.\nCursus sit amet dictum sit amet justo donec enim diam. Egestas erat imperdiet sed euismod. Nullam vehicula ipsum a arcu cursus vitae congue mauris. Habitasse platea dictumst vestibulum rhoncus est pellentesque elit. Duis ultricies lacus sed turpis tincidunt id aliquet risus feugiat. Faucibus ornare suspendisse sed nisi lacus sed viverra. Pretium fusce id velit ut tortor pretium viverra. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel. Senectus et netus et malesuada. Tellus pellentesque eu tincidunt tortor aliquam. Aenean sed adipiscing diam donec adipiscing tristique risus nec feugiat. Quis vel eros donec ac odio. Id interdum velit laoreet id donec ultrices tincidunt.\nMassa id neque aliquam vestibulum morbi blandit cursus risus at. Enim tortor at auctor urna nunc id cursus metus. Lorem ipsum dolor sit amet consectetur. At quis risus sed vulputate odio. Facilisis mauris sit amet massa vitae tortor condimentum lacinia quis. Et malesuada fames ac turpis egestas maecenas. Bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim. Viverra orci sagittis eu volutpat odio facilisis mauris. Adipiscing bibendum est ultricies integer quis auctor elit sed. Neque viverra justo nec ultrices dui sapien. Elementum nibh tellus molestie nunc non blandit massa enim. Euismod elementum nisi quis eleifend quam adipiscing vitae proin sagittis. Faucibus ornare suspendisse sed nisi. Quis viverra nibh cras pulvinar mattis nunc sed blandit. Tristique senectus et netus et. Magnis dis parturient montes nascetur ridiculus mus.\nDolor magna eget est lorem ipsum dolor. Nibh sit amet commodo nulla. Donec pretium vulputate sapien nec sagittis aliquam malesuada. Cras adipiscing enim eu turpis egestas pretium. Cras ornare arcu dui vivamus arcu felis bibendum ut tristique. Mus mauris vitae ultricies leo integer. In nulla posuere sollicitudin aliquam ultrices sagittis orci. Quis hendrerit dolor magna eget. Nisl tincidunt eget nullam non. Vitae congue eu consequat ac felis donec et odio. Vivamus at augue eget arcu dictum varius duis at. Ornare quam viverra orci sagittis.\nErat nam at lectus urna duis convallis. Massa placerat duis ultricies lacus sed turpis tincidunt id aliquet. Est ullamcorper eget nulla facilisi etiam dignissim diam. Arcu vitae elementum curabitur vitae nunc sed velit dignissim sodales. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus. Neque viverra justo nec ultrices dui sapien eget mi proin. Viverra accumsan in nisl nisi scelerisque eu ultrices. Consequat interdum varius sit amet mattis. In aliquam sem fringilla ut morbi. Eget arcu dictum varius duis at. Nulla aliquet porttitor lacus luctus accumsan tortor posuere. Arcu bibendum at varius vel pharetra vel turpis. Hac habitasse platea dictumst quisque sagittis purus sit amet. Sapien eget mi proin sed libero enim sed. Quam elementum pulvinar etiam non quam lacus suspendisse faucibus interdum. Semper viverra nam libero justo. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Et malesuada fames ac turpis egestas maecenas pharetra convallis posuere.\nTurpis egestas sed tempus urna et pharetra pharetra massa. Gravida in fermentum et sollicitudin ac orci phasellus. Ornare suspendisse sed nisi lacus sed viverra tellus in. Fames ac turpis egestas maecenas pharetra convallis posuere. Mi proin sed libero enim sed faucibus turpis. Sit amet mauris commodo quis imperdiet massa tincidunt nunc. Ut etiam sit amet nisl purus in mollis nunc. Habitasse platea dictumst quisque sagittis purus sit amet volutpat consequat. Eget aliquet nibh praesent tristique magna. Sit amet est placerat in egestas erat. Commodo sed egestas egestas fringilla. Enim nulla aliquet porttitor lacus luctus accumsan tortor posuere ac. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Dignissim convallis aenean et tortor at risus viverra. Morbi blandit cursus risus at ultrices mi. Ac turpis egestas integer eget aliquet nibh praesent tristique magna.\nVolutpat sed cras ornare arcu dui. Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam. Viverra justo nec ultrices dui sapien. Amet risus nullam eget felis eget nunc lobortis. Metus aliquam eleifend mi in. Ut eu sem integer vitae. Auctor elit sed vulputate mi sit amet. Nisl nisi scelerisque eu ultrices. Dictum fusce ut placerat orci nulla. Pellentesque habitant morbi tristique senectus et. Auctor elit sed vulputate mi sit. Tincidunt arcu non sodales neque. Mi in nulla posuere sollicitudin aliquam. Morbi non arcu risus quis varius quam quisque id diam. Cras adipiscing enim eu turpis egestas pretium aenean pharetra magna. At auctor urna nunc id cursus metus aliquam. Mauris a diam maecenas sed enim ut sem viverra. Nunc scelerisque viverra mauris in. In iaculis nunc sed augue lacus viverra vitae congue eu. Volutpat blandit aliquam etiam erat velit scelerisque in dictum non."), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); } } CLAY(CLAY_ID("Blob4Floating2"), { .floating = { .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, .zIndex = 1, .parentId = Clay_GetElementId(CLAY_STRING("SidebarBlob4")).id } }) { CLAY(CLAY_ID("ScrollContainer"), { .layout = { .sizing = { .height = CLAY_SIZING_FIXED(200) }, .childGap = 2 }, .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { CLAY(CLAY_ID("FloatingContainer2"), { .layout.sizing.height = CLAY_SIZING_GROW(), .floating = { .attachTo = CLAY_ATTACH_TO_PARENT, .zIndex = 1 } }) { CLAY(CLAY_ID("FloatingContainerInner"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16, 16, 16} }, .backgroundColor = {140,80, 200, 200} }) { CLAY_TEXT(CLAY_STRING("I'm an inline floating container."), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255,255,255,255} })); } } CLAY(CLAY_ID("ScrollContainerInner"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = {160, 160, 160, 255} }) { for (int i = 0; i < 100; i++) { RenderDropdownTextItem(i); } } } } Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("MainContent"))); if (scrollData.found) { CLAY(CLAY_ID("ScrollBar"), { .floating = { .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, .offset = { .y = -(scrollData.scrollPosition->y / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height }, .zIndex = 1, .parentId = Clay_GetElementId(CLAY_STRING("MainContent")).id, .attachPoints = { .element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } }) { CLAY(CLAY_ID("ScrollBarButton"), { .layout = { .sizing = {CLAY_SIZING_FIXED(12), CLAY_SIZING_FIXED((scrollData.scrollContainerDimensions.height / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height) }}, .backgroundColor = Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar"))) ? (Clay_Color){100, 100, 140, 150} : (Clay_Color){120, 120, 160, 150} , .cornerRadius = CLAY_CORNER_RADIUS(6) }) {} } } } return Clay_EndLayout(); } typedef struct { Clay_Vector2 clickOrigin; Clay_Vector2 positionOrigin; bool mouseDown; } ScrollbarData; ScrollbarData scrollbarData = {0}; bool debugEnabled = false; void UpdateDrawFrame(Font* fonts) { Vector2 mouseWheelDelta = GetMouseWheelMoveV(); float mouseWheelX = mouseWheelDelta.x; float mouseWheelY = mouseWheelDelta.y; if (IsKeyPressed(KEY_D)) { debugEnabled = !debugEnabled; Clay_SetDebugModeEnabled(debugEnabled); } //---------------------------------------------------------------------------------- // Handle scroll containers Clay_Vector2 mousePosition = RAYLIB_VECTOR2_TO_CLAY_VECTOR2(GetMousePosition()); Clay_SetPointerState(mousePosition, IsMouseButtonDown(0) && !scrollbarData.mouseDown); Clay_SetLayoutDimensions((Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }); if (!IsMouseButtonDown(0)) { scrollbarData.mouseDown = false; } if (IsMouseButtonDown(0) && !scrollbarData.mouseDown && Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) { Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("MainContent"))); scrollbarData.clickOrigin = mousePosition; scrollbarData.positionOrigin = *scrollContainerData.scrollPosition; scrollbarData.mouseDown = true; } else if (scrollbarData.mouseDown) { Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("MainContent"))); if (scrollContainerData.contentDimensions.height > 0) { Clay_Vector2 ratio = (Clay_Vector2) { scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width, scrollContainerData.contentDimensions.height / scrollContainerData.scrollContainerDimensions.height, }; if (scrollContainerData.config.vertical) { scrollContainerData.scrollPosition->y = scrollbarData.positionOrigin.y + (scrollbarData.clickOrigin.y - mousePosition.y) * ratio.y; } if (scrollContainerData.config.horizontal) { scrollContainerData.scrollPosition->x = scrollbarData.positionOrigin.x + (scrollbarData.clickOrigin.x - mousePosition.x) * ratio.x; } } } Clay_UpdateScrollContainers(true, (Clay_Vector2) {mouseWheelX, mouseWheelY}, GetFrameTime()); // Generate the auto layout for rendering double currentTime = GetTime(); Clay_RenderCommandArray renderCommands = CreateLayout(); printf("layout time: %f microseconds\n", (GetTime() - currentTime) * 1000 * 1000); // RENDERING --------------------------------- // currentTime = GetTime(); BeginDrawing(); ClearBackground(BLACK); Clay_Raylib_Render(renderCommands, fonts); EndDrawing(); // printf("render time: %f ms\n", (GetTime() - currentTime) * 1000); //---------------------------------------------------------------------------------- } bool reinitializeClay = false; void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); if (errorData.errorType == CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED) { reinitializeClay = true; Clay_SetMaxElementCount(Clay_GetMaxElementCount() * 2); } else if (errorData.errorType == CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED) { reinitializeClay = true; Clay_SetMaxMeasureTextCacheWordCount(Clay_GetMaxMeasureTextCacheWordCount() * 2); } } int main(void) { uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }, (Clay_ErrorHandler) { HandleClayErrors, 0 }); Clay_Raylib_Initialize(1024, 768, "Clay - Raylib Renderer Example", FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_MSAA_4X_HINT); profilePicture = LoadTexture("resources/profile-picture.png"); Font fonts[2]; fonts[FONT_ID_BODY_24] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400); SetTextureFilter(fonts[FONT_ID_BODY_24].texture, TEXTURE_FILTER_BILINEAR); fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 32, 0, 400); SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR); Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts); //-------------------------------------------------------------------------------------- // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { if (reinitializeClay) { Clay_SetMaxElementCount(8192); totalMemorySize = Clay_MinMemorySize(); clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }, (Clay_ErrorHandler) { HandleClayErrors, 0 }); reinitializeClay = false; } UpdateDrawFrame(fonts); } Clay_Raylib_Close(); return 0; } ================================================ FILE: examples/raylib-sidebar-scrolling-container/multi-compilation-unit.c ================================================ #include "../../clay.h" // NOTE: This file only exists to make sure that clay works when included in multiple translation units. void SatisfyCompiler(void) { CLAY(CLAY_ID("SatisfyCompiler"), { }) { CLAY_TEXT(CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .fontId = 0, .fontSize = 24 })); } } ================================================ FILE: examples/shared-layouts/clay-video-demo.c ================================================ #include "../../clay.h" #include const int FONT_ID_BODY_16 = 0; Clay_Color COLOR_WHITE = { 255, 255, 255, 255}; void RenderHeaderButton(Clay_String text) { CLAY_AUTO_ID({ .layout = { .padding = { 16, 16, 8, 8 }}, .backgroundColor = { 140, 140, 140, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(5) }) { CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_16, .fontSize = 16, .textColor = { 255, 255, 255, 255 } })); } } void RenderDropdownMenuItem(Clay_String text) { CLAY_AUTO_ID({.layout = { .padding = CLAY_PADDING_ALL(16)}}) { CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_16, .fontSize = 16, .textColor = { 255, 255, 255, 255 } })); } } typedef struct { Clay_String title; Clay_String contents; } Document; typedef struct { Document *documents; uint32_t length; } DocumentArray; Document documentsRaw[5]; DocumentArray documents = { .length = 5, .documents = documentsRaw }; typedef struct { intptr_t offset; intptr_t memory; } ClayVideoDemo_Arena; typedef struct { int32_t selectedDocumentIndex; float yOffset; ClayVideoDemo_Arena frameArena; } ClayVideoDemo_Data; typedef struct { int32_t requestedDocumentIndex; int32_t* selectedDocumentIndex; } SidebarClickData; void HandleSidebarInteraction( Clay_ElementId elementId, Clay_PointerData pointerData, void *userData ) { SidebarClickData *clickData = (SidebarClickData*)userData; // If this button was clicked if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { if (clickData->requestedDocumentIndex >= 0 && clickData->requestedDocumentIndex < documents.length) { // Select the corresponding document *clickData->selectedDocumentIndex = clickData->requestedDocumentIndex; } } } ClayVideoDemo_Data ClayVideoDemo_Initialize() { documents.documents[0] = (Document){ .title = CLAY_STRING("Squirrels"), .contents = CLAY_STRING("The Secret Life of Squirrels: Nature's Clever Acrobats\n""Squirrels are often overlooked creatures, dismissed as mere park inhabitants or backyard nuisances. Yet, beneath their fluffy tails and twitching noses lies an intricate world of cunning, agility, and survival tactics that are nothing short of fascinating. As one of the most common mammals in North America, squirrels have adapted to a wide range of environments from bustling urban centers to tranquil forests and have developed a variety of unique behaviors that continue to intrigue scientists and nature enthusiasts alike.\n""\n""Master Tree Climbers\n""At the heart of a squirrel's skill set is its impressive ability to navigate trees with ease. Whether they're darting from branch to branch or leaping across wide gaps, squirrels possess an innate talent for acrobatics. Their powerful hind legs, which are longer than their front legs, give them remarkable jumping power. With a tail that acts as a counterbalance, squirrels can leap distances of up to ten times the length of their body, making them some of the best aerial acrobats in the animal kingdom.\n""But it's not just their agility that makes them exceptional climbers. Squirrels' sharp, curved claws allow them to grip tree bark with precision, while the soft pads on their feet provide traction on slippery surfaces. Their ability to run at high speeds and scale vertical trunks with ease is a testament to the evolutionary adaptations that have made them so successful in their arboreal habitats.\n""\n""Food Hoarders Extraordinaire\n""Squirrels are often seen frantically gathering nuts, seeds, and even fungi in preparation for winter. While this behavior may seem like instinctual hoarding, it is actually a survival strategy that has been honed over millions of years. Known as \"scatter hoarding,\" squirrels store their food in a variety of hidden locations, often burying it deep in the soil or stashing it in hollowed-out tree trunks.\n""Interestingly, squirrels have an incredible memory for the locations of their caches. Research has shown that they can remember thousands of hiding spots, often returning to them months later when food is scarce. However, they don't always recover every stash some forgotten caches eventually sprout into new trees, contributing to forest regeneration. This unintentional role as forest gardeners highlights the ecological importance of squirrels in their ecosystems.\n""\n""The Great Squirrel Debate: Urban vs. Wild\n""While squirrels are most commonly associated with rural or wooded areas, their adaptability has allowed them to thrive in urban environments as well. In cities, squirrels have become adept at finding food sources in places like parks, streets, and even garbage cans. However, their urban counterparts face unique challenges, including traffic, predators, and the lack of natural shelters. Despite these obstacles, squirrels in urban areas are often observed using human infrastructure such as buildings, bridges, and power lines as highways for their acrobatic escapades.\n""There is, however, a growing concern regarding the impact of urban life on squirrel populations. Pollution, deforestation, and the loss of natural habitats are making it more difficult for squirrels to find adequate food and shelter. As a result, conservationists are focusing on creating squirrel-friendly spaces within cities, with the goal of ensuring these resourceful creatures continue to thrive in both rural and urban landscapes.\n""\n""A Symbol of Resilience\n""In many cultures, squirrels are symbols of resourcefulness, adaptability, and preparation. Their ability to thrive in a variety of environments while navigating challenges with agility and grace serves as a reminder of the resilience inherent in nature. Whether you encounter them in a quiet forest, a city park, or your own backyard, squirrels are creatures that never fail to amaze with their endless energy and ingenuity.\n""In the end, squirrels may be small, but they are mighty in their ability to survive and thrive in a world that is constantly changing. So next time you spot one hopping across a branch or darting across your lawn, take a moment to appreciate the remarkable acrobat at work a true marvel of the natural world.\n") }; documents.documents[1] = (Document){ .title = CLAY_STRING("Lorem Ipsum"), .contents = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") }; documents.documents[2] = (Document){ .title = CLAY_STRING("Vacuum Instructions"), .contents = CLAY_STRING("Chapter 3: Getting Started - Unpacking and Setup\n""\n""Congratulations on your new SuperClean Pro 5000 vacuum cleaner! In this section, we will guide you through the simple steps to get your vacuum up and running. Before you begin, please ensure that you have all the components listed in the \"Package Contents\" section on page 2.\n""\n""1. Unboxing Your Vacuum\n""Carefully remove the vacuum cleaner from the box. Avoid using sharp objects that could damage the product. Once removed, place the unit on a flat, stable surface to proceed with the setup. Inside the box, you should find:\n""\n"" The main vacuum unit\n"" A telescoping extension wand\n"" A set of specialized cleaning tools (crevice tool, upholstery brush, etc.)\n"" A reusable dust bag (if applicable)\n"" A power cord with a 3-prong plug\n"" A set of quick-start instructions\n""\n""2. Assembling Your Vacuum\n""Begin by attaching the extension wand to the main body of the vacuum cleaner. Line up the connectors and twist the wand into place until you hear a click. Next, select the desired cleaning tool and firmly attach it to the wand's end, ensuring it is securely locked in.\n""\n""For models that require a dust bag, slide the bag into the compartment at the back of the vacuum, making sure it is properly aligned with the internal mechanism. If your vacuum uses a bagless system, ensure the dust container is correctly seated and locked in place before use.\n""\n""3. Powering On\n""To start the vacuum, plug the power cord into a grounded electrical outlet. Once plugged in, locate the power switch, usually positioned on the side of the handle or body of the unit, depending on your model. Press the switch to the \"On\" position, and you should hear the motor begin to hum. If the vacuum does not power on, check that the power cord is securely plugged in, and ensure there are no blockages in the power switch.\n""\n""Note: Before first use, ensure that the vacuum filter (if your model has one) is properly installed. If unsure, refer to \"Section 5: Maintenance\" for filter installation instructions.") }; documents.documents[3] = (Document){ .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") }; documents.documents[4] = (Document){ .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") }; ClayVideoDemo_Data data = { .frameArena = { .memory = (intptr_t)malloc(1024) } }; return data; } Clay_RenderCommandArray ClayVideoDemo_CreateLayout(ClayVideoDemo_Data *data) { data->frameArena.offset = 0; Clay_BeginLayout(); Clay_Sizing layoutExpand = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }; Clay_Color contentBackgroundColor = { 90, 90, 90, 255 }; // Build UI here CLAY(CLAY_ID("OuterContainer"), { .backgroundColor = {43, 41, 51, 255 }, .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = layoutExpand, .padding = CLAY_PADDING_ALL(16), .childGap = 16 } }) { // Child elements go inside braces CLAY(CLAY_ID("HeaderBar"), { .layout = { .sizing = { .height = CLAY_SIZING_FIXED(60), .width = CLAY_SIZING_GROW(0) }, .padding = { 16, 16, 0, 0 }, .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = contentBackgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { // Header buttons go here CLAY(CLAY_ID("FileButton"), { .layout = { .padding = { 16, 16, 8, 8 }}, .backgroundColor = {140, 140, 140, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(5) }) { CLAY_TEXT(CLAY_STRING("File"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_16, .fontSize = 16, .textColor = { 255, 255, 255, 255 } })); bool fileMenuVisible = Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileButton"))) || Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileMenu"))); if (fileMenuVisible) { // Below has been changed slightly to fix the small bug where the menu would dismiss when mousing over the top gap CLAY(CLAY_ID("FileMenu"), { .floating = { .attachTo = CLAY_ATTACH_TO_PARENT, .attachPoints = { .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM }, }, .layout = { .padding = {0, 0, 8, 8 } } }) { CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(200) }, }, .backgroundColor = {40, 40, 40, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { // Render dropdown items here RenderDropdownMenuItem(CLAY_STRING("New")); RenderDropdownMenuItem(CLAY_STRING("Open")); RenderDropdownMenuItem(CLAY_STRING("Close")); } } } } RenderHeaderButton(CLAY_STRING("Edit")); CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0) }}}) {} RenderHeaderButton(CLAY_STRING("Upload")); RenderHeaderButton(CLAY_STRING("Media")); RenderHeaderButton(CLAY_STRING("Support")); } CLAY(CLAY_ID("LowerContent"), { .layout = { .sizing = layoutExpand, .childGap = 16 } }) { CLAY(CLAY_ID("Sidebar"), { .backgroundColor = contentBackgroundColor, .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = CLAY_PADDING_ALL(16), .childGap = 8, .sizing = { .width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_GROW(0) } } }) { for (int i = 0; i < documents.length; i++) { Document document = documents.documents[i]; Clay_LayoutConfig sidebarButtonLayout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16) }; if (i == data->selectedDocumentIndex) { CLAY_AUTO_ID({ .layout = sidebarButtonLayout, .backgroundColor = {120, 120, 120, 255 }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_16, .fontSize = 20, .textColor = { 255, 255, 255, 255 } })); } } else { SidebarClickData *clickData = (SidebarClickData *)(data->frameArena.memory + data->frameArena.offset); *clickData = (SidebarClickData) { .requestedDocumentIndex = i, .selectedDocumentIndex = &data->selectedDocumentIndex }; data->frameArena.offset += sizeof(SidebarClickData); CLAY_AUTO_ID({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color) { 120, 120, 120, (float)(Clay_Hovered() ? 120 : 0) }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { Clay_OnHover(HandleSidebarInteraction, clickData); CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_16, .fontSize = 20, .textColor = { 255, 255, 255, 255 } })); } } } } CLAY(CLAY_ID("MainContent"), { .backgroundColor = contentBackgroundColor, .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16, .padding = CLAY_PADDING_ALL(16), .sizing = layoutExpand } }) { Document selectedDocument = documents.documents[data->selectedDocumentIndex]; CLAY_TEXT(selectedDocument.title, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_16, .fontSize = 24, .textColor = COLOR_WHITE })); CLAY_TEXT(selectedDocument.contents, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_16, .fontSize = 24, .textColor = COLOR_WHITE })); } } } Clay_RenderCommandArray renderCommands = Clay_EndLayout(); for (int32_t i = 0; i < renderCommands.length; i++) { Clay_RenderCommandArray_Get(&renderCommands, i)->boundingBox.y += data->yOffset; } return renderCommands; } ================================================ FILE: examples/sokol-corner-radius/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(sokol_corner_radius C) if(CMAKE_SYSTEM_NAME STREQUAL Windows) add_executable(sokol_corner_radius WIN32 main.c) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT sokol_corner_radius) else() add_executable(sokol_corner_radius main.c) endif() target_link_libraries(sokol_corner_radius PUBLIC sokol) ================================================ FILE: examples/sokol-corner-radius/main.c ================================================ #include "sokol_app.h" #include "sokol_gfx.h" #include "sokol_glue.h" #include "sokol_log.h" #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "util/sokol_gl.h" #include "fontstash.h" #include "util/sokol_fontstash.h" #define SOKOL_CLAY_IMPL #include "../../renderers/sokol/sokol_clay.h" static void init() { sg_setup(&(sg_desc){ .environment = sglue_environment(), .logger.func = slog_func, }); sgl_setup(&(sgl_desc_t){ .logger.func = slog_func, }); sclay_setup(); uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0}); Clay_SetMeasureTextFunction(sclay_measure_text, NULL); } Clay_RenderCommandArray CornerRadiusTest(){ Clay_BeginLayout(); Clay_Sizing layoutExpand = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }; CLAY(CLAY_ID("OuterContainer"), { .backgroundColor = {43, 41, 51, 255}, .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = layoutExpand, .padding = {0, 0, 20, 20}, .childGap = 20 } }) { for(int i = 0; i < 6; ++i){ CLAY(CLAY_IDI("Row", i), { .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT, .sizing = layoutExpand, .padding = {20, 20, 0, 0}, .childGap = 20 } }) { for(int j = 0; j < 6; ++j){ CLAY(CLAY_IDI("Tile", i*6+j), { .backgroundColor = {120, 140, 255, 128}, .cornerRadius = {(i%3)*15, (j%3)*15, (i/2)*15, (j/2)*15}, .border = { .color = {120, 140, 255, 255}, .width = {3, 9, 6, 12, 0}, }, .layout = { .sizing = layoutExpand } }); } } } } return Clay_EndLayout(); } static void frame() { sclay_new_frame(); Clay_RenderCommandArray renderCommands = CornerRadiusTest(); sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() }); sgl_matrix_mode_modelview(); sgl_load_identity(); sclay_render(renderCommands, NULL); sgl_draw(); sg_end_pass(); sg_commit(); } static void event(const sapp_event *ev) { if(ev->type == SAPP_EVENTTYPE_KEY_DOWN && ev->key_code == SAPP_KEYCODE_D){ Clay_SetDebugModeEnabled(true); } else { sclay_handle_event(ev); } } static void cleanup() { sclay_shutdown(); sgl_shutdown(); sg_shutdown(); } sapp_desc sokol_main(int argc, char **argv) { return (sapp_desc){ .init_cb = init, .frame_cb = frame, .event_cb = event, .cleanup_cb = cleanup, .window_title = "Clay - Corner Radius Test", .width = 800, .height = 600, .icon.sokol_default = true, .logger.func = slog_func, }; } ================================================ FILE: examples/sokol-video-demo/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(sokol_video_demo C) include(FetchContent) set(FETCHCONTENT_QUIET FALSE) # Linux -pthread shenanigans if (CMAKE_SYSTEM_NAME STREQUAL Linux) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) endif() FetchContent_Declare( fontstash GIT_REPOSITORY "https://github.com/memononen/fontstash.git" GIT_TAG "b5ddc9741061343740d85d636d782ed3e07cf7be" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(fontstash) FetchContent_Declare( sokol GIT_REPOSITORY "https://github.com/floooh/sokol.git" GIT_TAG "master" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(sokol) set(sokol_HEADERS ${sokol_SOURCE_DIR}/sokol_app.h ${sokol_SOURCE_DIR}/sokol_gfx.h ${sokol_SOURCE_DIR}/sokol_glue.h ${sokol_SOURCE_DIR}/sokol_log.h ${sokol_SOURCE_DIR}/util/sokol_gl.h ${fontstash_SOURCE_DIR}/src/fontstash.h ${sokol_SOURCE_DIR}/util/sokol_fontstash.h) if(CMAKE_SYSTEM_NAME STREQUAL Darwin) add_library(sokol STATIC sokol.c ${sokol_HEADERS}) target_compile_options(sokol PRIVATE -x objective-c) target_link_libraries(sokol PUBLIC "-framework QuartzCore" "-framework Cocoa" "-framework MetalKit" "-framework Metal") else() add_library(sokol STATIC sokol.c ${sokol_HEADERS}) if (CMAKE_SYSTEM_NAME STREQUAL Linux) target_compile_definitions(sokol PRIVATE SOKOL_GLCORE=1) target_link_libraries(sokol INTERFACE X11 Xi Xcursor GL dl m) target_link_libraries(sokol PUBLIC Threads::Threads) endif() endif() target_include_directories(sokol INTERFACE ${sokol_SOURCE_DIR} ${fontstash_SOURCE_DIR}/src PRIVATE ${sokol_SOURCE_DIR} ${fontstash_SOURCE_DIR}/src) if(CMAKE_SYSTEM_NAME STREQUAL Windows) add_executable(sokol_video_demo WIN32 main.c) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT sokol_video_demo) else() add_executable(sokol_video_demo main.c) endif() target_link_libraries(sokol_video_demo PUBLIC sokol) add_custom_command( TARGET sokol_video_demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources) ================================================ FILE: examples/sokol-video-demo/main.c ================================================ #include "sokol_app.h" #include "sokol_gfx.h" #include "sokol_glue.h" #include "sokol_log.h" #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "util/sokol_gl.h" #include "fontstash.h" #include "util/sokol_fontstash.h" #define SOKOL_CLAY_IMPL #include "../../renderers/sokol/sokol_clay.h" #include "../shared-layouts/clay-video-demo.c" static ClayVideoDemo_Data demoData; static sclay_font_t fonts[1]; static void init() { sg_setup(&(sg_desc){ .environment = sglue_environment(), .logger.func = slog_func, }); sgl_setup(&(sgl_desc_t){ .logger.func = slog_func, }); sclay_setup(); uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0}); fonts[FONT_ID_BODY_16] = sclay_add_font("resources/Roboto-Regular.ttf"); Clay_SetMeasureTextFunction(sclay_measure_text, &fonts); demoData = ClayVideoDemo_Initialize(); } static void frame() { sclay_new_frame(); Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() }); sgl_matrix_mode_modelview(); sgl_load_identity(); sclay_render(renderCommands, fonts); sgl_draw(); sg_end_pass(); sg_commit(); } static void event(const sapp_event *ev) { if(ev->type == SAPP_EVENTTYPE_KEY_DOWN && ev->key_code == SAPP_KEYCODE_D){ Clay_SetDebugModeEnabled(true); } else { sclay_handle_event(ev); } } static void cleanup() { sclay_shutdown(); sgl_shutdown(); sg_shutdown(); } sapp_desc sokol_main(int argc, char **argv) { return (sapp_desc){ .init_cb = init, .frame_cb = frame, .event_cb = event, .cleanup_cb = cleanup, .window_title = "Clay - Sokol Renderer Example", .width = 800, .height = 600, .high_dpi = true, .icon.sokol_default = true, .logger.func = slog_func, }; } ================================================ FILE: examples/sokol-video-demo/sokol.c ================================================ #define SOKOL_IMPL #if defined(_WIN32) #define SOKOL_D3D11 #elif defined(__EMSCRIPTEN__) #define SOKOL_GLES2 #elif defined(__APPLE__) #define SOKOL_METAL #else #define SOKOL_GLCORE33 #endif #include "sokol_app.h" #include "sokol_gfx.h" #include "sokol_time.h" #include "sokol_fetch.h" #include "sokol_glue.h" #include "sokol_log.h" #include "util/sokol_gl.h" #include // fontstash requires this #include // fontstash requires this #define FONTSTASH_IMPLEMENTATION #include "fontstash.h" #define SOKOL_FONTSTASH_IMPL #include "util/sokol_fontstash.h" ================================================ FILE: examples/termbox2-demo/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.25) project(clay_examples_termbox2_demo C) set(CMAKE_C_STANDARD 11) include(FetchContent) set(FETCHCONTENT_QUIET FALSE) FetchContent_Declare( termbox2 GIT_REPOSITORY "https://github.com/termbox/termbox2.git" GIT_TAG "ffd159c2a6106dd5eef338a6702ad15d4d4aa809" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(termbox2) FetchContent_Declare( stb GIT_REPOSITORY "https://github.com/nothings/stb.git" GIT_TAG "fede005abaf93d9d7f3a679d1999b2db341b360f" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(stb) add_executable(clay_examples_termbox2_demo main.c) target_compile_options(clay_examples_termbox2_demo PUBLIC) target_include_directories(clay_examples_termbox2_demo PUBLIC . PRIVATE ${termbox2_SOURCE_DIR} PRIVATE ${stb_SOURCE_DIR}) target_link_libraries(clay_examples_termbox2_demo PRIVATE m) # Used by stb_image.h add_custom_command( TARGET clay_examples_termbox2_demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources) ================================================ FILE: examples/termbox2-demo/main.c ================================================ /* Unlicense Copyright (c) 2025 Mivirl This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to */ #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "../../renderers/termbox2/clay_renderer_termbox2.c" #define TB_IMPL #include "termbox2.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define STB_IMAGE_RESIZE_IMPLEMENTATION #include "stb_image_resize2.h" // ------------------------------------------------------------------------------------------------- // -- Internal state // If the program should exit the main render/interaction loop bool end_loop = false; // If the debug tools should be displayed bool debugMode = false; // ------------------------------------------------------------------------------------------------- // -- Clay components void component_text_pair(const char *key, const char *value) { size_t keylen = strlen(key); size_t vallen = strlen(value); Clay_String keytext = (Clay_String) { .length = keylen, .chars = key, }; Clay_String valtext = (Clay_String) { .length = vallen, .chars = value, }; Clay_TextElementConfig *textconfig = CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }); CLAY_AUTO_ID({ .layout = { .sizing = { .width = { .size.minMax = { .min = strlen("Border chars CLAY_TB_IMAGE_MODE_UNICODE_FAST") * Clay_Termbox_Cell_Width(), } }, } }, }) { CLAY_TEXT(keytext, textconfig); CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_GROW(1) } }) { } CLAY_TEXT(valtext, textconfig); } } void component_termbox_settings(void) { CLAY(CLAY_ID("Termbox Settings"), { .floating = { .attachTo = CLAY_ATTACH_TO_PARENT, .zIndex = 1, .attachPoints = { CLAY_ATTACH_POINT_CENTER_CENTER, CLAY_ATTACH_POINT_CENTER_TOP }, .offset = { 0, 0 } }, }) { CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_FIT(), .padding = { 6 * Clay_Termbox_Cell_Width(), 6 * Clay_Termbox_Cell_Width(), 2 * Clay_Termbox_Cell_Height(), 2 * Clay_Termbox_Cell_Height(), } }, .border = { .width = CLAY_BORDER_ALL(1), .color = { 0x00, 0x00, 0x00, 0xff } }, .backgroundColor = { 0x7f, 0x00, 0x00, 0x7f } }) { const char *color_mode = NULL; switch (clay_tb_color_mode) { case TB_OUTPUT_NORMAL: { color_mode = "TB_OUTPUT_NORMAL"; break; } case TB_OUTPUT_256: { color_mode = "TB_OUTPUT_256"; break; } case TB_OUTPUT_216: { color_mode = "TB_OUTPUT_216"; break; } case TB_OUTPUT_GRAYSCALE: { color_mode = "TB_OUTPUT_GRAYSCALE"; break; } case TB_OUTPUT_TRUECOLOR: { color_mode = "TB_OUTPUT_TRUECOLOR"; break; } case CLAY_TB_OUTPUT_NOCOLOR: { color_mode = "CLAY_TB_OUTPUT_NOCOLOR"; break; } default: { color_mode = "INVALID"; break; } } const char *border_mode = NULL; switch (clay_tb_border_mode) { case CLAY_TB_BORDER_MODE_ROUND: { border_mode = "CLAY_TB_BORDER_MODE_ROUND"; break; } case CLAY_TB_BORDER_MODE_MINIMUM: { border_mode = "CLAY_TB_BORDER_MODE_MINIMUM"; break; } default: { border_mode = "INVALID"; break; } } const char *border_chars = NULL; switch (clay_tb_border_chars) { case CLAY_TB_BORDER_CHARS_ASCII: { border_chars = "CLAY_TB_BORDER_CHARS_ASCII"; break; } case CLAY_TB_BORDER_CHARS_UNICODE: { border_chars = "CLAY_TB_BORDER_CHARS_UNICODE"; break; } case CLAY_TB_BORDER_CHARS_BLANK: { border_chars = "CLAY_TB_BORDER_CHARS_BLANK"; break; } case CLAY_TB_BORDER_CHARS_NONE: { border_chars = "CLAY_TB_BORDER_CHARS_NONE"; break; } default: { border_chars = "INVALID"; break; } } const char *image_mode = NULL; switch (clay_tb_image_mode) { case CLAY_TB_IMAGE_MODE_PLACEHOLDER: { image_mode = "CLAY_TB_IMAGE_MODE_PLACEHOLDER"; break; } case CLAY_TB_IMAGE_MODE_BG: { image_mode = "CLAY_TB_IMAGE_MODE_BG"; break; } case CLAY_TB_IMAGE_MODE_ASCII_FG: { image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG"; break; } case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: { image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG_FAST"; break; } case CLAY_TB_IMAGE_MODE_ASCII: { image_mode = "CLAY_TB_IMAGE_MODE_ASCII"; break; } case CLAY_TB_IMAGE_MODE_ASCII_FAST: { image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FAST"; break; } case CLAY_TB_IMAGE_MODE_UNICODE: { image_mode = "CLAY_TB_IMAGE_MODE_UNICODE"; break; } case CLAY_TB_IMAGE_MODE_UNICODE_FAST: { image_mode = "CLAY_TB_IMAGE_MODE_UNICODE_FAST"; break; } default: { image_mode = "INVALID"; break; } } const char *transparency = NULL; if (clay_tb_transparency) { transparency = "true"; } else { transparency = "false"; } CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM }, }) { component_text_pair("Color mode", color_mode); component_text_pair("Border mode", border_mode); component_text_pair("Border chars", border_chars); component_text_pair("Image mode", image_mode); component_text_pair("Transparency", transparency); } } } } void component_color_palette(void) { CLAY_AUTO_ID({ .layout = { .childGap = 16, .padding = { 2 * Clay_Termbox_Cell_Width(), 2 * Clay_Termbox_Cell_Width(), 2 * Clay_Termbox_Cell_Height(), 2 * Clay_Termbox_Cell_Height(), } }, .border = { .width = CLAY_BORDER_OUTSIDE(2), .color = { 0x00, 0x00, 0x00, 0xff } }, .backgroundColor = { 0x7f, 0x7f, 0x7f, 0xff } }) { for (int type = 0; type < 2; ++type) { CLAY_AUTO_ID({ .layout ={ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = CLAY_SIZING_FIT(), .childGap = Clay_Termbox_Cell_Height() }, }) { for (float ri = 0; ri < 4; ri += 1) { CLAY_AUTO_ID({ .layout ={ .sizing = CLAY_SIZING_FIT(), .childGap = Clay_Termbox_Cell_Width() }, }) { for (float r = ri * 0x44; r < (ri + 1) * 0x44; r += 0x22) { CLAY_AUTO_ID({ .layout ={ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = CLAY_SIZING_FIT(), }, }) { for (float g = 0; g < 0xff; g += 0x22) { CLAY_AUTO_ID({ .layout ={ .sizing = CLAY_SIZING_FIT(), }, }) { for (float b = 0; b < 0xff; b += 0x22) { Clay_Color color = { r, g, b, 0x7f }; Clay_LayoutConfig layout = (Clay_LayoutConfig) { .sizing = { .width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()), .height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height()) } }; if (0 == type) { CLAY_AUTO_ID({ .layout = layout, .backgroundColor = color }) {} } else if (1 == type) { CLAY_AUTO_ID({ .layout = layout, }) { CLAY_TEXT(CLAY_STRING("#"), CLAY_TEXT_CONFIG({ .textColor = color })); } } } } } } } } } } } } } void component_unicode_text(void) { CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_FIT(), .padding = { 2 * Clay_Termbox_Cell_Width(), 2 * Clay_Termbox_Cell_Width(), 2 * Clay_Termbox_Cell_Height(), 2 * Clay_Termbox_Cell_Height(), } }, .backgroundColor = { 0xcc, 0xbb, 0xaa, 0xff }, .border = { // This border should still be displayed in CLAY_TB_BORDER_MODE_ROUND mode .width = { 0.75 * Clay_Termbox_Cell_Width(), 0.75 * Clay_Termbox_Cell_Width(), 0.75 * Clay_Termbox_Cell_Height(), 0.75 * Clay_Termbox_Cell_Height(), }, .color = { 0x33, 0x33, 0x33, 0xff }, }, }) { CLAY_TEXT( CLAY_STRING("Non-ascii character tests:\n" "\n" "(from https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html)\n" " Mathematics and Sciences:\n" " ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),\n" " ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (A ⇔ B),\n" " 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm\n" "\n" " Compact font selection example text:\n" " ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789\n" " abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ\n" " –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд\n" " ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა\n" "\n" "(from https://blog.denisbider.com/2015/09/when-monospace-fonts-arent-unicode.html):\n" " aeioucsz\n" " áéíóúčšž\n" " 台北1234\n" " QRS12\n" " アイウ1234\n" "\n" "(from https://stackoverflow.com/a/1644280)\n" " ٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃)." ), CLAY_TEXT_CONFIG({ .textColor = { 0x11, 0x11, 0x11, 0xff } }) ); } } void component_keybinds(void) { CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_FIT(), .padding = { 4 * Clay_Termbox_Cell_Width(), 4 * Clay_Termbox_Cell_Width(), 2 * Clay_Termbox_Cell_Height(), 2 * Clay_Termbox_Cell_Height(), } }, .backgroundColor = { 0x00, 0x7f, 0x7f, 0xff } }) { CLAY_TEXT( CLAY_STRING( "Termbox2 renderer test\n" "\n" "Keybinds:\n" " c/C - Cycle through color modes\n" " b/B - Cycle through border modes\n" " h/H - Cycle through border characters\n" " i/I - Cycle through image modes\n" " t/T - Toggle transparency\n" " d/D - Toggle debug mode\n" " q/Q - Quit\n" ), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff }}) ); } } void component_image(clay_tb_image *image, int width) { CLAY_AUTO_ID({ .layout = { .sizing = { .width = (0 == width) ? CLAY_SIZING_GROW() : CLAY_SIZING_FIXED(width), }, }, .image = { .imageData = image, }, .aspectRatio = { 512.0 / 406.0 } }) { } } void component_mouse_data(void) { CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(), }, }, }) { Clay_Context* context = Clay_GetCurrentContext(); Clay_Vector2 mouse_position = context->pointerInfo.position; Clay_LayoutConfig layout = (Clay_LayoutConfig) { .sizing = { .width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()), .height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height()) } }; float v = 255 * mouse_position.x / Clay_Termbox_Width(); v = (0 > v) ? 0 : v; v = (255 < v) ? 255 : v; Clay_Color color = (Clay_Color) { v, v, v, 0xff }; CLAY_AUTO_ID({ .layout = layout, .backgroundColor = color }) {} v = 255 * mouse_position.y / Clay_Termbox_Height(); v = (0 > v) ? 0 : v; v = (255 < v) ? 255 : v; color = (Clay_Color) { v, v, v, 0xff }; CLAY_AUTO_ID({ .layout = layout, .backgroundColor = color }) {} } } void component_bordered_text(void) { CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIT(450), .height = CLAY_SIZING_FIT(), }, .padding = CLAY_PADDING_ALL(32) }, .backgroundColor = { 0x24, 0x55, 0x34, 0xff }, }) { CLAY_AUTO_ID({ .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0xaa, 0x00, 0x00, 0xff } }, }) { CLAY_TEXT( CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } })); } CLAY_AUTO_ID({ .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0xaa, 0x00, 0xff } }, }) { CLAY_TEXT(CLAY_STRING("of the border width"), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } })); } CLAY_AUTO_ID({ .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } }, }) { CLAY_TEXT(CLAY_STRING("and overlap for multiple lines\nof text"), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } })); } CLAY_AUTO_ID({ .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } }, }) { CLAY_TEXT(CLAY_STRING("this text\nis long enough\nto display all\n borders\naround it"), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } })); } } } Clay_RenderCommandArray CreateLayout(clay_tb_image *image1, clay_tb_image *image2) { Clay_BeginLayout(); CLAY_AUTO_ID({ .clip = { .vertical = false, .horizontal = true, .childOffset = Clay_GetScrollOffset(), }, .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER }, .childGap = 64 }, .backgroundColor = { 0x24, 0x24, 0x24, 0xff } }) { CLAY_AUTO_ID({ .layout = { .childAlignment = { .x = CLAY_ALIGN_X_RIGHT, }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = CLAY_SIZING_FIT(), }, }) { component_keybinds(); component_unicode_text(); } CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 32, .sizing = CLAY_SIZING_FIT(), }, }) { component_termbox_settings(); component_image(image1, 150); component_image(image2, 0); component_mouse_data(); component_bordered_text(); } component_color_palette(); } return Clay_EndLayout(); } // ------------------------------------------------------------------------------------------------- // -- Interactive functions void handle_clay_errors(Clay_ErrorData errorData) { Clay_Termbox_Close(); fprintf(stderr, "%s", errorData.errorText.chars); exit(1); } /** Process events received from termbox2 and handle interaction */ void handle_termbox_events(void) { // Wait up to 100ms for an event (key/mouse press, terminal resize) before continuing // If an event is already available, this doesn't wait. Will not wait due to the previous call // to termbox_waitfor_event. Increasing the wait time reduces load without reducing // responsiveness (but will of course prevent other code from running on this thread while it's // waiting) struct tb_event evt; int ms_to_wait = 0; int err = tb_peek_event(&evt, ms_to_wait); switch (err) { default: case TB_ERR_NO_EVENT: { break; } case TB_ERR_POLL: { if (EINTR != tb_last_errno()) { Clay_Termbox_Close(); fprintf(stderr, "Failed to read event from TTY\n"); exit(1); } break; } case TB_OK: { switch (evt.type) { case TB_EVENT_RESIZE: { Clay_SetLayoutDimensions((Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() }); break; } case TB_EVENT_KEY: { if (TB_KEY_CTRL_C == evt.key) { end_loop = true; break; } switch (evt.ch) { case 'q': case 'Q': { end_loop = true; break; } case 'd': case 'D': { debugMode = !debugMode; Clay_SetDebugModeEnabled(debugMode); break; } case 'c': { int new_mode = clay_tb_color_mode - 1; new_mode = (0 <= new_mode) ? new_mode : TB_OUTPUT_TRUECOLOR; Clay_Termbox_Set_Color_Mode(new_mode); break; } case 'C': { int new_mode = (clay_tb_color_mode + 1) % (TB_OUTPUT_TRUECOLOR + 1); Clay_Termbox_Set_Color_Mode(new_mode); break; } case 'b': { enum border_mode new_mode = clay_tb_border_mode - 1; new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode) ? new_mode : CLAY_TB_BORDER_MODE_MINIMUM; Clay_Termbox_Set_Border_Mode(new_mode); break; } case 'B': { enum border_mode new_mode = (clay_tb_border_mode + 1) % (CLAY_TB_BORDER_MODE_MINIMUM + 1); new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode) ? new_mode : CLAY_TB_BORDER_MODE_ROUND; Clay_Termbox_Set_Border_Mode(new_mode); break; } case 'h': { enum border_chars new_chars = clay_tb_border_chars - 1; new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars) ? new_chars : CLAY_TB_BORDER_CHARS_NONE; Clay_Termbox_Set_Border_Chars(new_chars); break; } case 'H': { enum border_chars new_chars = (clay_tb_border_chars + 1) % (CLAY_TB_BORDER_CHARS_NONE + 1); new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars) ? new_chars : CLAY_TB_BORDER_CHARS_ASCII; Clay_Termbox_Set_Border_Chars(new_chars); break; } case 'i': { enum image_mode new_mode = clay_tb_image_mode - 1; new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode) ? new_mode : CLAY_TB_IMAGE_MODE_UNICODE_FAST; Clay_Termbox_Set_Image_Mode(new_mode); break; } case 'I': { enum image_mode new_mode = (clay_tb_image_mode + 1) % (CLAY_TB_IMAGE_MODE_UNICODE_FAST + 1); new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode) ? new_mode : CLAY_TB_IMAGE_MODE_PLACEHOLDER; Clay_Termbox_Set_Image_Mode(new_mode); break; } case 't': case 'T': { Clay_Termbox_Set_Transparency(!clay_tb_transparency); } } break; } case TB_EVENT_MOUSE: { Clay_Vector2 mousePosition = { (float)evt.x * Clay_Termbox_Cell_Width(), (float)evt.y * Clay_Termbox_Cell_Height() }; // Mouse release events may not be produced by all terminals, and will // be sent during hover, so can't be used to detect when the mouse has // been released switch (evt.key) { case TB_KEY_MOUSE_LEFT: { Clay_SetPointerState(mousePosition, true); break; } case TB_KEY_MOUSE_RIGHT: { Clay_SetPointerState(mousePosition, false); break; } case TB_KEY_MOUSE_MIDDLE: { Clay_SetPointerState(mousePosition, false); break; } case TB_KEY_MOUSE_RELEASE: { Clay_SetPointerState(mousePosition, false); break; } case TB_KEY_MOUSE_WHEEL_UP: { Clay_Vector2 scrollDelta = { 0.5 * Clay_Termbox_Cell_Width(), 0 }; Clay_UpdateScrollContainers(false, scrollDelta, 1); break; } case TB_KEY_MOUSE_WHEEL_DOWN: { Clay_Vector2 scrollDelta = { -0.5 * Clay_Termbox_Cell_Width(), 0 }; Clay_UpdateScrollContainers(false, scrollDelta, 1); break; } default: { break; } } break; } default: { break; } } break; } } } int main(void) { clay_tb_image shark_image1 = Clay_Termbox_Image_Load_File("resources/512px-Shark_antwerp_zoo.jpeg"); clay_tb_image shark_image2 = Clay_Termbox_Image_Load_File("resources/512px-Shark_antwerp_zoo.jpeg"); if (NULL == shark_image1.pixel_data) { exit(1); } if (NULL == shark_image2.pixel_data) { exit(1); } int num_elements = 3 * 8192; Clay_SetMaxElementCount(num_elements); uint64_t size = Clay_MinMemorySize(); void *memory = malloc(size); if (NULL == memory) { exit(1); } Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(size, memory); Clay_Termbox_Initialize( TB_OUTPUT_256, CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_CHARS_DEFAULT, CLAY_TB_IMAGE_MODE_DEFAULT, false); Clay_Initialize(clay_arena, (Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() }, (Clay_ErrorHandler) { handle_clay_errors, NULL }); Clay_SetMeasureTextFunction(Clay_Termbox_MeasureText, NULL); // Initial render before waiting for events Clay_RenderCommandArray commands = CreateLayout(&shark_image1, &shark_image2); Clay_Termbox_Render(commands); tb_present(); while (!end_loop) { // Block until event is available. Optional, but reduces load since this demo is purely // synchronous to user input. Clay_Termbox_Waitfor_Event(); handle_termbox_events(); commands = CreateLayout(&shark_image1, &shark_image2); tb_clear(); Clay_Termbox_Render(commands); tb_present(); } Clay_Termbox_Close(); Clay_Termbox_Image_Free(&shark_image1); Clay_Termbox_Image_Free(&shark_image2); free(memory); return 0; } ================================================ FILE: examples/termbox2-demo/readme.md ================================================ # Termbox2 renderer demo Terminal-based renderer using [termbox2](https://github.com/termbox/termbox2) This demo shows a color palette and a few different components. It allows changing configuration settings for colors, border size rounding mode, characters used for borders, and transparency. ``` Keybinds: c/C - Cycle through color modes b/B - Cycle through border modes h/H - Cycle through border characters i/I - Cycle through image modes t/T - Toggle transparency d/D - Toggle debug mode q/Q - Quit ``` Configuration can be also be overriden by environment variables: - `CLAY_TB_COLOR_MODE` - `NORMAL` - `256` - `216` - `GRAYSCALE` - `TRUECOLOR` - `NOCOLOR` - `CLAY_TB_BORDER_CHARS` - `DEFAULT` - `ASCII` - `UNICODE` - `NONE` - `CLAY_TB_IMAGE_MODE` - `DEFAULT` - `PLACEHOLDER` - `BG` - `ASCII_FG` - `ASCII` - `UNICODE` - `ASCII_FG_FAST` - `ASCII_FAST` - `UNICODE_FAST` - `CLAY_TB_TRANSPARENCY` - `1` - `0` - `CLAY_TB_CELL_PIXELS` - `widthxheight` ## Building Build the binary with cmake ```sh mkdir build cd build cmake .. make ``` Then run the executable: ```sh ./clay_examples_termbox2_demo ``` ## Attributions Resources used: - `512px-Shark_antwerp_zoo.jpeg` - Retrieved from - License: [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/deed.en) - No changes made ================================================ FILE: examples/termbox2-image-demo/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.25) project(clay_examples_termbox2_image_demo C) set(CMAKE_C_STANDARD 11) set(CMAKE_BUILD_TYPE Release) set(CMAKE_C_FLAGS_RELEASE "-O3") include(FetchContent) set(FETCHCONTENT_QUIET FALSE) FetchContent_Declare( termbox2 GIT_REPOSITORY "https://github.com/termbox/termbox2.git" GIT_TAG "ffd159c2a6106dd5eef338a6702ad15d4d4aa809" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(termbox2) FetchContent_Declare( stb GIT_REPOSITORY "https://github.com/nothings/stb.git" GIT_TAG "fede005abaf93d9d7f3a679d1999b2db341b360f" GIT_PROGRESS TRUE GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(stb) add_executable(clay_examples_termbox2_image_demo main.c) target_compile_options(clay_examples_termbox2_image_demo PUBLIC) target_include_directories(clay_examples_termbox2_image_demo PUBLIC . PRIVATE ${termbox2_SOURCE_DIR} PRIVATE ${stb_SOURCE_DIR}) target_link_libraries(clay_examples_termbox2_image_demo PRIVATE m) # Used by stb_image.h add_custom_command( TARGET clay_examples_termbox2_image_demo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources) ================================================ FILE: examples/termbox2-image-demo/main.c ================================================ /* Unlicense Copyright (c) 2025 Mivirl This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to */ #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "../../renderers/termbox2/clay_renderer_termbox2.c" #define TB_IMPL #include "termbox2.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define STB_IMAGE_RESIZE_IMPLEMENTATION #include "stb_image_resize2.h" // ------------------------------------------------------------------------------------------------- // -- Data structures struct img_group { clay_tb_image thumbnail; clay_tb_image image; clay_tb_image image_1; clay_tb_image image_2; int width; int height; }; typedef struct img_group img_group; // ------------------------------------------------------------------------------------------------- // -- Internal state // If the program should exit the main render/interaction loop bool end_loop = false; // If the debug tools should be displayed bool debugMode = false; // ------------------------------------------------------------------------------------------------- // -- Internal utility functions img_group img_group_load(const char *filename) { img_group rv; rv.thumbnail = Clay_Termbox_Image_Load_File(filename); rv.image = Clay_Termbox_Image_Load_File(filename); rv.image_1 = Clay_Termbox_Image_Load_File(filename); rv.image_2 = Clay_Termbox_Image_Load_File(filename); if (NULL == rv.thumbnail.pixel_data || NULL == rv.image.pixel_data || NULL == rv.image_1.pixel_data || NULL == rv.image_2.pixel_data) { exit(1); } rv.width = rv.image.pixel_width; rv.height = rv.image.pixel_height; return rv; } void img_group_free(img_group *img) { Clay_Termbox_Image_Free(&img->thumbnail); Clay_Termbox_Image_Free(&img->image); Clay_Termbox_Image_Free(&img->image_1); Clay_Termbox_Image_Free(&img->image_2); } // ------------------------------------------------------------------------------------------------- // -- Clay components void component_text_pair(const char *key, const char *value) { size_t keylen = strlen(key); size_t vallen = strlen(value); Clay_String keytext = (Clay_String) { .length = keylen, .chars = key, }; Clay_String valtext = (Clay_String) { .length = vallen, .chars = value, }; Clay_TextElementConfig *textconfig = CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }); CLAY_AUTO_ID({ .layout = { .sizing = { .width = { .size.minMax = { .min = strlen("Border chars CLAY_TB_IMAGE_MODE_UNICODE_FAST") * Clay_Termbox_Cell_Width(), } }, } }, }) { CLAY_TEXT(keytext, textconfig); CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_GROW(1) } }) { } CLAY_TEXT(valtext, textconfig); } } void component_termbox_settings(void) { CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_FIT(), .padding = { 6 * Clay_Termbox_Cell_Width(), 6 * Clay_Termbox_Cell_Width(), 2 * Clay_Termbox_Cell_Height(), 2 * Clay_Termbox_Cell_Height(), } }, .border = { .width = CLAY_BORDER_ALL(1), .color = { 0x00, 0x00, 0x00, 0xff } }, .backgroundColor = { 0x7f, 0x00, 0x00, 0x7f } }) { const char *color_mode = NULL; switch (clay_tb_color_mode) { case TB_OUTPUT_NORMAL: { color_mode = "TB_OUTPUT_NORMAL"; break; } case TB_OUTPUT_256: { color_mode = "TB_OUTPUT_256"; break; } case TB_OUTPUT_216: { color_mode = "TB_OUTPUT_216"; break; } case TB_OUTPUT_GRAYSCALE: { color_mode = "TB_OUTPUT_GRAYSCALE"; break; } case TB_OUTPUT_TRUECOLOR: { color_mode = "TB_OUTPUT_TRUECOLOR"; break; } case CLAY_TB_OUTPUT_NOCOLOR: { color_mode = "CLAY_TB_OUTPUT_NOCOLOR"; break; } default: { color_mode = "INVALID"; break; } } const char *border_mode = NULL; switch (clay_tb_border_mode) { case CLAY_TB_BORDER_MODE_ROUND: { border_mode = "CLAY_TB_BORDER_MODE_ROUND"; break; } case CLAY_TB_BORDER_MODE_MINIMUM: { border_mode = "CLAY_TB_BORDER_MODE_MINIMUM"; break; } default: { border_mode = "INVALID"; break; } } const char *border_chars = NULL; switch (clay_tb_border_chars) { case CLAY_TB_BORDER_CHARS_ASCII: { border_chars = "CLAY_TB_BORDER_CHARS_ASCII"; break; } case CLAY_TB_BORDER_CHARS_UNICODE: { border_chars = "CLAY_TB_BORDER_CHARS_UNICODE"; break; } case CLAY_TB_BORDER_CHARS_BLANK: { border_chars = "CLAY_TB_BORDER_CHARS_BLANK"; break; } case CLAY_TB_BORDER_CHARS_NONE: { border_chars = "CLAY_TB_BORDER_CHARS_NONE"; break; } default: { border_chars = "INVALID"; break; } } const char *image_mode = NULL; switch (clay_tb_image_mode) { case CLAY_TB_IMAGE_MODE_PLACEHOLDER: { image_mode = "CLAY_TB_IMAGE_MODE_PLACEHOLDER"; break; } case CLAY_TB_IMAGE_MODE_BG: { image_mode = "CLAY_TB_IMAGE_MODE_BG"; break; } case CLAY_TB_IMAGE_MODE_ASCII_FG: { image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG"; break; } case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: { image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG_FAST"; break; } case CLAY_TB_IMAGE_MODE_ASCII: { image_mode = "CLAY_TB_IMAGE_MODE_ASCII"; break; } case CLAY_TB_IMAGE_MODE_ASCII_FAST: { image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FAST"; break; } case CLAY_TB_IMAGE_MODE_UNICODE: { image_mode = "CLAY_TB_IMAGE_MODE_UNICODE"; break; } case CLAY_TB_IMAGE_MODE_UNICODE_FAST: { image_mode = "CLAY_TB_IMAGE_MODE_UNICODE_FAST"; break; } default: { image_mode = "INVALID"; break; } } const char *transparency = NULL; if (clay_tb_transparency) { transparency = "true"; } else { transparency = "false"; } CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM }, }) { component_text_pair("Color mode", color_mode); component_text_pair("Border mode", border_mode); component_text_pair("Border chars", border_chars); component_text_pair("Image mode", image_mode); component_text_pair("Transparency", transparency); } } } void component_keybinds(void) { CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_FIT(), .padding = { 4 * Clay_Termbox_Cell_Width(), 4 * Clay_Termbox_Cell_Width(), 2 * Clay_Termbox_Cell_Height(), 2 * Clay_Termbox_Cell_Height(), } }, .backgroundColor = { 0x00, 0x7f, 0x7f, 0xff } }) { CLAY_TEXT( CLAY_STRING( "Termbox2 renderer test\n" "\n" "Keybinds:\n" " up/down arrows - Change selected image\n" " c/C - Cycle through color modes\n" " b/B - Cycle through border modes\n" " h/H - Cycle through border characters\n" " i/I - Cycle through image modes\n" " t/T - Toggle transparency\n" " d/D - Toggle debug mode\n" " q/Q - Quit\n" ), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff }}) ); } } void component_image(img_group *img_pair) { CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, .childGap = 1 * Clay_Termbox_Cell_Height() }, .backgroundColor = { 0x24, 0x24, 0x24, 0xff } }) { CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(), }, }, .image = { .imageData = &img_pair->image, }, .aspectRatio = { (float)img_pair->width / img_pair->height } }) { } component_keybinds(); } } void component_image_small(img_group **img_pairs, int count, int selected_index) { CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_PERCENT(0.25), }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 20, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, }, }) { CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_PERCENT(0.7), }, }, .image = { .imageData = &img_pairs[selected_index]->image_1, }, .aspectRatio = { (float)img_pairs[selected_index]->width / img_pairs[selected_index]->height } }) { } CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(), }, }, .image = { .imageData = &img_pairs[selected_index]->image_2, }, .aspectRatio = { (float)img_pairs[selected_index]->width / img_pairs[selected_index]->height } }) { } component_termbox_settings(); } } void component_thumbnails(img_group **img_pairs, int count, int selected_index) { CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_PERCENT(0.1), .height = CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 20 }, .backgroundColor = { 0x42, 0x42, 0x42, 0xff } }) { for (int i = 0; i < count; ++i) { Clay_BorderElementConfig border; if (i == selected_index) { border = (Clay_BorderElementConfig) { .width =CLAY_BORDER_OUTSIDE(10), .color = { 0x00, 0x30, 0xc0, 0x8f } }; } else { border = (Clay_BorderElementConfig) { .width = CLAY_BORDER_OUTSIDE(0), }; } CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(), }, }, .border = border, .image = { .imageData = &img_pairs[i]->thumbnail, }, .aspectRatio = { (float)img_pairs[i]->width / img_pairs[i]->height } }) { } } } } int selected_thumbnail = 0; const int thumbnail_count = 5; Clay_RenderCommandArray CreateLayout(struct img_group **imgs) { Clay_BeginLayout(); CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER }, .childGap = 64 }, .backgroundColor = { 0x24, 0x24, 0x24, 0xff } }) { component_thumbnails(imgs, thumbnail_count, selected_thumbnail); component_image_small(imgs, thumbnail_count, selected_thumbnail); component_image(imgs[selected_thumbnail]); } return Clay_EndLayout(); } // ------------------------------------------------------------------------------------------------- // -- Interactive functions void handle_clay_errors(Clay_ErrorData errorData) { Clay_Termbox_Close(); fprintf(stderr, "%s", errorData.errorText.chars); exit(1); } /** Process events received from termbox2 and handle interaction */ void handle_termbox_events(void) { // Wait up to 100ms for an event (key/mouse press, terminal resize) before continuing // If an event is already available, this doesn't wait. Will not wait due to the previous call // to termbox_waitfor_event. Increasing the wait time reduces load without reducing // responsiveness (but will of course prevent other code from running on this thread while it's // waiting) struct tb_event evt; int ms_to_wait = 0; int err = tb_peek_event(&evt, ms_to_wait); switch (err) { default: case TB_ERR_NO_EVENT: { break; } case TB_ERR_POLL: { if (EINTR != tb_last_errno()) { Clay_Termbox_Close(); fprintf(stderr, "Failed to read event from TTY\n"); exit(1); } break; } case TB_OK: { switch (evt.type) { case TB_EVENT_RESIZE: { Clay_SetLayoutDimensions((Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() }); break; } case TB_EVENT_KEY: { if (TB_KEY_CTRL_C == evt.key) { end_loop = true; break; } if (0 != evt.ch) { switch (evt.ch) { case 'q': case 'Q': { end_loop = true; break; } case 'd': case 'D': { debugMode = !debugMode; Clay_SetDebugModeEnabled(debugMode); break; } case 'c': { int new_mode = clay_tb_color_mode - 1; new_mode = (0 <= new_mode) ? new_mode : TB_OUTPUT_TRUECOLOR; Clay_Termbox_Set_Color_Mode(new_mode); break; } case 'C': { int new_mode = (clay_tb_color_mode + 1) % (TB_OUTPUT_TRUECOLOR + 1); Clay_Termbox_Set_Color_Mode(new_mode); break; } case 'b': { enum border_mode new_mode = clay_tb_border_mode - 1; new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode) ? new_mode : CLAY_TB_BORDER_MODE_MINIMUM; Clay_Termbox_Set_Border_Mode(new_mode); break; } case 'B': { enum border_mode new_mode = (clay_tb_border_mode + 1) % (CLAY_TB_BORDER_MODE_MINIMUM + 1); new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode) ? new_mode : CLAY_TB_BORDER_MODE_ROUND; Clay_Termbox_Set_Border_Mode(new_mode); break; } case 'h': { enum border_chars new_chars = clay_tb_border_chars - 1; new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars) ? new_chars : CLAY_TB_BORDER_CHARS_NONE; Clay_Termbox_Set_Border_Chars(new_chars); break; } case 'H': { enum border_chars new_chars = (clay_tb_border_chars + 1) % (CLAY_TB_BORDER_CHARS_NONE + 1); new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars) ? new_chars : CLAY_TB_BORDER_CHARS_ASCII; Clay_Termbox_Set_Border_Chars(new_chars); break; } case 'i': { enum image_mode new_mode = clay_tb_image_mode - 1; new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode) ? new_mode : CLAY_TB_IMAGE_MODE_UNICODE_FAST; Clay_Termbox_Set_Image_Mode(new_mode); break; } case 'I': { enum image_mode new_mode = (clay_tb_image_mode + 1) % (CLAY_TB_IMAGE_MODE_UNICODE_FAST + 1); new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode) ? new_mode : CLAY_TB_IMAGE_MODE_PLACEHOLDER; Clay_Termbox_Set_Image_Mode(new_mode); break; } case 't': case 'T': { Clay_Termbox_Set_Transparency(!clay_tb_transparency); } } } else if (0 != evt.key) { switch (evt.key) { case TB_KEY_ARROW_UP: { selected_thumbnail = (selected_thumbnail > 0) ? selected_thumbnail - 1 : 0; break; } case TB_KEY_ARROW_DOWN: { selected_thumbnail = (selected_thumbnail < thumbnail_count - 1) ? selected_thumbnail + 1 : thumbnail_count - 1; break; } } } break; } case TB_EVENT_MOUSE: { Clay_Vector2 mousePosition = { (float)evt.x * Clay_Termbox_Cell_Width(), (float)evt.y * Clay_Termbox_Cell_Height() }; // Mouse release events may not be produced by all terminals, and will // be sent during hover, so can't be used to detect when the mouse has // been released switch (evt.key) { case TB_KEY_MOUSE_LEFT: { Clay_SetPointerState(mousePosition, true); break; } case TB_KEY_MOUSE_RIGHT: { Clay_SetPointerState(mousePosition, false); break; } case TB_KEY_MOUSE_MIDDLE: { Clay_SetPointerState(mousePosition, false); break; } case TB_KEY_MOUSE_RELEASE: { Clay_SetPointerState(mousePosition, false); break; } case TB_KEY_MOUSE_WHEEL_UP: { Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() }; Clay_UpdateScrollContainers(false, scrollDelta, 1); break; } case TB_KEY_MOUSE_WHEEL_DOWN: { Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() }; Clay_UpdateScrollContainers(false, scrollDelta, 1); break; } default: { break; } } break; } default: { break; } } break; } } } int main(void) { img_group *imgs[thumbnail_count]; img_group img_shark = img_group_load("resources/512px-Shark_antwerp_zoo.jpeg"); img_group img_castle = img_group_load("resources/512px-Balmoral_Castle_30_July_2011.jpeg"); img_group img_dog = img_group_load("resources/512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg"); img_group img_rosa = img_group_load("resources/512px-Rosa_Cubana_2018-09-21_1610.jpeg"); img_group img_vorderer = img_group_load("resources/512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg"); imgs[0] = &img_shark; imgs[1] = &img_castle; imgs[2] = &img_dog; imgs[3] = &img_rosa; imgs[4] = &img_vorderer; int num_elements = 3 * 8192; Clay_SetMaxElementCount(num_elements); uint64_t size = Clay_MinMemorySize(); void *memory = malloc(size); if (NULL == memory) { exit(1); } Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(size, memory); Clay_Termbox_Initialize( TB_OUTPUT_256, CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_CHARS_DEFAULT, CLAY_TB_IMAGE_MODE_DEFAULT, false); Clay_Initialize(clay_arena, (Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() }, (Clay_ErrorHandler) { handle_clay_errors, NULL }); Clay_SetMeasureTextFunction(Clay_Termbox_MeasureText, NULL); // Initial render before waiting for events Clay_RenderCommandArray commands = CreateLayout(imgs); Clay_Termbox_Render(commands); tb_present(); while (!end_loop) { // Block until event is available. Optional, but reduces load since this demo is purely // synchronous to user input. Clay_Termbox_Waitfor_Event(); handle_termbox_events(); commands = CreateLayout(imgs); Clay_Termbox_Render(commands); tb_present(); } Clay_Termbox_Close(); img_group_free(&img_shark); img_group_free(&img_castle); img_group_free(&img_dog); img_group_free(&img_rosa); img_group_free(&img_vorderer); free(memory); return 0; } ================================================ FILE: examples/termbox2-image-demo/readme.md ================================================ # Termbox2 renderer demo Terminal-based renderer using [termbox2](https://github.com/termbox/termbox2) This demo shows a color palette and a few different components. It allows changing configuration settings for colors, border size rounding mode, characters used for borders, and transparency. ``` Keybinds: c/C - Cycle through color modes b/B - Cycle through border modes h/H - Cycle through border characters i/I - Cycle through image modes t/T - Toggle transparency d/D - Toggle debug mode q/Q - Quit ``` Configuration can be also be overriden by environment variables: - `CLAY_TB_COLOR_MODE` - `NORMAL` - `256` - `216` - `GRAYSCALE` - `TRUECOLOR` - `NOCOLOR` - `CLAY_TB_BORDER_CHARS` - `DEFAULT` - `ASCII` - `UNICODE` - `NONE` - `CLAY_TB_IMAGE_MODE` - `DEFAULT` - `PLACEHOLDER` - `BG` - `ASCII_FG` - `ASCII` - `UNICODE` - `ASCII_FG_FAST` - `ASCII_FAST` - `UNICODE_FAST` - `CLAY_TB_TRANSPARENCY` - `1` - `0` - `CLAY_TB_CELL_PIXELS` - `widthxheight` ## Building Build the binary with cmake ```sh mkdir build cd build cmake .. make ``` Then run the executable: ```sh ./clay_examples_termbox2_demo ``` ## Attributions Resources used: - `512px-Shark_antwerp_zoo.jpeg` - Retrieved from - License: [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/) - No changes made - `512px-Balmoral_Castle_30_July_2011.jpg` - Retrieved from - License: [Creative Commons Attribution-ShareAlike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/) - No changes made - `512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg` - Retrieved from - License: [Creative Commons Attribution-ShareAlike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/) - No changes made - `512px-Rosa_Cubana_2018-09-21_1610.jpeg` - Retrieved from - License: [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/) - No changes made - `512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg` - Retrieved from - License: [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/) - No changes made ================================================ FILE: examples/terminal-example/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(clay_examples_terminal C) set(CMAKE_C_STANDARD 99) add_executable(clay_examples_terminal main.c) target_compile_options(clay_examples_terminal PUBLIC) target_include_directories(clay_examples_terminal PUBLIC .) if (CMAKE_SYSTEM_NAME STREQUAL Linux) target_link_libraries(clay_examples_terminal INTERFACE m) endif() target_link_libraries(clay_examples_terminal PUBLIC ${CURSES_LIBRARY}) set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG") set(CMAKE_CXX_FLAGS_RELEASE "-O3") ================================================ FILE: examples/terminal-example/main.c ================================================ // Must be defined in one file, _before_ #include "clay.h" #define CLAY_IMPLEMENTATION #include #include "../../clay.h" #include "../../renderers/terminal/clay_renderer_terminal_ansi.c" #include "../shared-layouts/clay-video-demo.c" const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); } int main() { const int width = 145; const int height = 41; int columnWidth = 16; uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); Clay_Initialize(arena, (Clay_Dimensions) {.width = (float) width * columnWidth, .height = (float) height * columnWidth}, (Clay_ErrorHandler) {HandleClayErrors}); // Tell clay how to measure text Clay_SetMeasureTextFunction(Console_MeasureText, &columnWidth); ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize(); while (true) { Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); Clay_Terminal_Render(renderCommands, width, height, columnWidth); fflush(stdout); sleep(1); } } ================================================ FILE: examples/win32_gdi/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.27) project(win32_gdi C) set(CMAKE_C_STANDARD 99) add_executable(win32_gdi WIN32 main.c) target_compile_options(win32_gdi PUBLIC) target_include_directories(win32_gdi PUBLIC .) add_custom_command( TARGET win32_gdi POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_BINARY_DIR}/resources) ================================================ FILE: examples/win32_gdi/build.ps1 ================================================ # to build this, install mingw gcc main.c -ggdb -omain -lgdi32 -lmingw32 # -mwindows # comment -mwindows out for console output ================================================ FILE: examples/win32_gdi/main.c ================================================ #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include "../../renderers/win32_gdi/clay_renderer_gdi.c" #define CLAY_IMPLEMENTATION #include "../../clay.h" #include "../shared-layouts/clay-video-demo.c" ClayVideoDemo_Data demo_data; #define APPNAME "Clay GDI Example" char szAppName[] = APPNAME; // The name of this application char szTitle[] = APPNAME; // The title bar text void CenterWindow(HWND hWnd); long lastMsgTime = 0; bool ui_debug_mode; HFONT fonts[1]; #ifndef RECTWIDTH #define RECTWIDTH(rc) ((rc).right - (rc).left) #endif #ifndef RECTHEIGHT #define RECTHEIGHT(rc) ((rc).bottom - (rc).top) #endif LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { // ----------------------- first and last case WM_CREATE: CenterWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_MOUSEWHEEL: // scrolling data { short zDelta = GET_WHEEL_DELTA_WPARAM(wParam); // todo: i think GetMessageTime can roll over, so something like if(lastmsgtime > now) ... may be needed long now = GetMessageTime(); float dt = (now - lastMsgTime) / 1000.00; lastMsgTime = now; // little hacky hack to make scrolling *feel* right if (abs(zDelta) > 100) { zDelta = zDelta * .012; } Clay_UpdateScrollContainers(true, (Clay_Vector2){.x = 0, .y = zDelta}, dt); InvalidateRect(hwnd, NULL, false); // force a wm_paint event break; } case WM_RBUTTONUP: case WM_LBUTTONUP: case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MOUSEMOVE: // mouse events { short mouseX = GET_X_LPARAM(lParam); short mouseY = GET_Y_LPARAM(lParam); short mouseButtons = LOWORD(wParam); Clay_SetPointerState((Clay_Vector2){mouseX, mouseY}, mouseButtons & 0b01); InvalidateRect(hwnd, NULL, false); // force a wm_paint event break; } case WM_SIZE: // resize events { RECT r = {0}; if (GetClientRect(hwnd, &r)) { Clay_Dimensions dim = (Clay_Dimensions){.height = r.bottom - r.top, .width = r.right - r.left}; Clay_SetLayoutDimensions(dim); } InvalidateRect(hwnd, NULL, false); // force a wm_paint event break; } case WM_KEYDOWN: if (VK_ESCAPE == wParam) { DestroyWindow(hwnd); break; } if (wParam == VK_F12) { Clay_SetDebugModeEnabled(ui_debug_mode = !ui_debug_mode); break; } printf("Key Pressed: %d\r\n", wParam); InvalidateRect(hwnd, NULL, false); // force a wm_paint event break; // ----------------------- render case WM_PAINT: { Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data); Clay_Win32_Render(hwnd, renderCommands, fonts); break; } // ----------------------- let windows do all other stuff default: return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } bool didAllocConsole = false; void HandleClayErrors(Clay_ErrorData errorData) { if (!didAllocConsole) { didAllocConsole = AllocConsole(); } printf("Handle Clay Errors: %s\r\n", errorData.errorText.chars); } int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASS wc; HWND hwnd; demo_data = ClayVideoDemo_Initialize(); uint64_t clayRequiredMemory = Clay_MinMemorySize(); Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory)); Clay_Initialize(clayMemory, (Clay_Dimensions){.width = 800, .height = 600}, (Clay_ErrorHandler){HandleClayErrors}); // This final argument is new since the video was published Clay_Win32_SetRendererFlags(CLAYGDI_RF_ALPHABLEND | CLAYGDI_RF_SMOOTHCORNERS); // Initialize clay fonts and text drawing fonts[FONT_ID_BODY_16] = Clay_Win32_SimpleCreateFont("resources/Roboto-Regular.ttf", "Roboto", -11, FW_NORMAL); Clay_SetMeasureTextFunction(Clay_Win32_MeasureText, fonts); ZeroMemory(&wc, sizeof wc); wc.hInstance = hInstance; wc.lpszClassName = szAppName; wc.lpfnWndProc = (WNDPROC)WndProc; wc.style = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW; wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); if (FALSE == RegisterClass(&wc)) return 0; // Calculate window rectangle by given client size // TODO: AdjustWindowRectExForDpi for DPI support RECT rcWindow = { .right = 800, .bottom = 600 }; AdjustWindowRect(&rcWindow, WS_OVERLAPPEDWINDOW, FALSE); hwnd = CreateWindow( szAppName, szTitle, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, RECTWIDTH(rcWindow), // CW_USEDEFAULT, RECTHEIGHT(rcWindow), // CW_USEDEFAULT, 0, 0, hInstance, 0); if (hwnd == NULL) return 0; // Main message loop: while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } void CenterWindow(HWND hwnd_self) { HWND hwnd_parent; RECT rw_self, rc_parent, rw_parent; int xpos, ypos; hwnd_parent = GetParent(hwnd_self); if (NULL == hwnd_parent) hwnd_parent = GetDesktopWindow(); GetWindowRect(hwnd_parent, &rw_parent); GetClientRect(hwnd_parent, &rc_parent); GetWindowRect(hwnd_self, &rw_self); xpos = rw_parent.left + (rc_parent.right + rw_self.left - rw_self.right) / 2; ypos = rw_parent.top + (rc_parent.bottom + rw_self.top - rw_self.bottom) / 2; SetWindowPos( hwnd_self, NULL, xpos, ypos, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } //+--------------------------------------------------------------------------- ================================================ FILE: renderers/GLES3/clay_renderer_gles3.h ================================================ #ifndef CLAY_RENDERER_GLES3_H #define CLAY_RENDERER_GLES3_H // There may be custom header customizations, very client specific // let client indicate that they manage headers by setting GLSL_VERSION #ifndef GLSL_VERSION #if defined(__EMSCRIPTEN__) #include #include #include #define GLSL_VERSION "#version 300 es" #else // Only apple computers now sorry // That means it is not really GLES3 but desktop OpenGL 3 // Luckily, it is compatible with GLES3 #include #define GLSL_VERSION "#version 330 core" #endif #endif #define MAX_IMAGES 4 #define MAX_FONTS 4 /* * Instanced rendering for Rects/Images/Borders * will use this data * Note, it needs to be padded to 4 floats * Draws: * - One rectangular with possibly rounded corner * - And possibly with a hole inside (with rounded edges too, if corners are rounded) * - It could also draw a picture with alsoe rounded corner */ typedef struct RectInstance { float x, y, w, h; // 4 Draw where on screen float u0, v0, u1, v1; // 4 Atlas region float r, g, b, a; // 4 Color float radiusTL, radiusTR; // 2 Corner rounding float radiusBL, radiusBR; // 2 float borderL, borderR; // 2 Border widths float borderT, borderB; // 2 float texToUse; // 1 Texture atlas to take an image from (1-4) float pad[3]; // 3 } RectInstance; /* * Struct for glyph instanced rendering * Each glyph consists of 6 vertexes (to make 2 triangle of a quad) */ typedef struct GlyphVtx { float x, y; // To draw Where float u, v; // To draw What float r, g, b, a; // Text color float atlasTexUnit; // Shader will have all samples loaded but this will point which to use float pad[3]; // 3 } GlyphVtx; typedef struct Gles3_GlyphVtxArray { GlyphVtx *instData; int capacity; int count; } Gles3_GlyphVtxArray; typedef struct Gles3_QuadInstanceArray { RectInstance *instData; // packed per-instance floats int capacity; // how many instances it can hold int count; // how many instances does it actually hold } Gles3_QuadInstanceArray; typedef struct Gles3_ImageConfig { int textureToUse; float u0, v0; float u1, v1; } Gles3_ImageConfig; #ifndef CLAY_RENDERER_GLES3_IMPLEMENTATION typedef struct Gles3_Renderer Gles3_Renderer; #endif #ifdef CLAY_RENDERER_GLES3_IMPLEMENTATION #include #include #include #include "clay_renderer_gles3.h" enum { ATTR_QUAD_POS = 0, ATTR_QUAD_RECT = 1, ATTR_QUAD_COLOR = 2, ATTR_QUAD_UV = 3, ATTR_QUAD_RAD = 4, ATTR_QUAD_BORDER = 5, ATTR_QUAD_TEX = 6, }; enum { ATTR_GLYPH_POS = 0, ATTR_GLYPH_UV = 1, ATTR_GLYPH_COLOR = 2, ATTR_GLYPH_TEX = 3, }; /* * rendering */ const char *GLES3_QUAD_VERTEX_SHADER = GLSL_VERSION "\n" "precision mediump float;\n" "layout(location = 0) in vec2 aPos; // unit quad (0..1)\n" "layout(location = 1) in vec4 aRect; // x,y,w,h (pixels)\n" "layout(location = 3) in vec4 aUV; // u0,v0,u1,v1\n" "layout(location = 2) in vec4 aColor; // rgba\n" "layout(location = 4) in vec4 aCornerRadii;\n" "layout(location = 5) in vec4 aBorderWidths;\n" "layout(location = 6) in float aTexSlot;\n" "uniform vec2 uScreen; // screen size in pixels\n" "out vec2 vPos;\n" "out vec4 vRect;\n" "out vec4 vColor;\n" "out vec2 vUV;\n" "out vec4 vCornerRadii;\n" "out vec4 vBorderWidths;\n" "out float vTexSlot;\n" "void main() {\n" " vec2 pos = vec2(aPos.x * aRect.z + aRect.x, aPos.y * aRect.w + aRect.y);\n" " vec2 ndc = pos / uScreen * 2.0 - 1.0; // ndc.y increases up; pos y increases down (we will inve\n" " ndc.y = -ndc.y;\n" " gl_Position = vec4(ndc, 0.0, 1.0);\n" " vPos = aPos;\n" " vRect = aRect;\n" " vColor = aColor;\n" " vUV = mix(aUV.xy, aUV.zw, aPos);\n" " vCornerRadii = aCornerRadii;\n" " vBorderWidths = aBorderWidths;\n" " vTexSlot = aTexSlot;\n" "}\n"; const char *GLES3_QUAD_FRAGMENT_SHADER = GLSL_VERSION "\n" "precision mediump float;\n" "in vec2 vPos;\n" "in vec4 vRect;\n" "in vec4 vColor;\n" "in vec2 vUV;\n" "in vec4 vCornerRadii;\n" "in vec4 vBorderWidths;\n" "in float vTexSlot;\n" "uniform sampler2D uTex0;\n" "uniform sampler2D uTex1;\n" "uniform sampler2D uTex2;\n" "uniform sampler2D uTex3;\n" "out vec4 frag;\n" "void main() {\n" " // Pixel coordinates in pixel space\n" " vec2 pix = vRect.xy + vPos * vRect.zw;\n" " float x0 = vRect.x;\n" " float y0 = vRect.y;\n" " float w = vRect.z;\n" " float h = vRect.w;\n" " // Local position inside the rectangle (0..w, 0..h)\n" " vec2 local = pix - vec2(x0, y0);\n" " // Original corner radii\n" " float tl = vCornerRadii.x;\n" " float tr = vCornerRadii.y;\n" " float bl = vCornerRadii.z;\n" " float br = vCornerRadii.w;\n" " // Border thicknesses\n" " float L = vBorderWidths.x;\n" " float R = vBorderWidths.y;\n" " float T = vBorderWidths.z;\n" " float B = vBorderWidths.w;\n" " bool CLAY_BORDERS_ARE_INSET = true; // it is true\n" " bool isBorder = (L > 0.0 || R > 0.0 || T > 0.0 || B > 0.0);\n" " float outerAlpha = 1.0;\n" " // If it is not a border but rect or image, then it only has outer border what is provided\n" " // Otherwise it increases the outter border, but the provided borde is the ineer border\n" " // I think is better not increase rounding radius when that radius is smaller than border thickness\n" " float outter_tl;\n" " float outter_tr;\n" " float outter_bl;\n" " float outter_br;\n" " if (CLAY_BORDERS_ARE_INSET) {\n" " // Actural behaviour\n" " outter_tl = tl;\n" " outter_tr = tr;\n" " outter_bl = bl;\n" " outter_br = br;\n" " tl = (tl > min(T, L)) ? tl - min(T, L) : tl;\n" " tr = (tr > min(T, R)) ? tr - min(T, R) : tr;\n" " bl = (bl > min(B, L)) ? bl - min(B, L) : bl;\n" " br = (br > min(B, R)) ? br - min(B, R) : br;\n" " } else {\n" " // Hypothetical behaviour\n" " outter_tl = (tl > min(T, L)) ? tl + min(T, L) : tl;\n" " outter_tr = (tr > min(T, R)) ? tr + min(T, R) : tr;\n" " outter_bl = (bl > min(B, L)) ? bl + min(B, L) : bl;\n" " outter_br = (br > min(B, R)) ? br + min(B, R) : br;\n" " }\n" " if (outter_tl > 0.0 && local.x < outter_tl && local.y < outter_tl)\n" " outerAlpha = step(length(local - vec2(outter_tl, outter_tl)), outter_tl);\n" " if (outter_tr > 0.0 && local.x > w - outter_tr && local.y < outter_tr)\n" " outerAlpha *= step(length(local - vec2(w - outter_tr, outter_tr)), outter_tr);\n" " if (outter_bl > 0.0 && local.x < outter_bl && local.y > h - outter_bl)\n" " outerAlpha *= step(length(local - vec2(outter_bl, h - outter_bl)), outter_bl);\n" " if (outter_br > 0.0 && local.x > w - outter_br && local.y > h - outter_br)\n" " outerAlpha *= step(length(local - vec2(w - outter_br, h - outter_br)), outter_br);\n" " if (outerAlpha < 0.5)\n" " discard;\n" " // -------- Border logic --------\n" " if (isBorder) {\n" " float iw = w - L - R;\n" " float ih = h - T - B;\n" " vec2 innerLocal = local - vec2(L, T);\n" " // Check if pixel is inside inner rounded rect\n" " bool insideInner = true;\n" " if (tl > 0.0 && innerLocal.x < tl && innerLocal.y < tl)\n" " insideInner = (length(innerLocal - vec2(tl, tl)) <= tl);\n" " if (tr > 0.0 && innerLocal.x > iw - tr && innerLocal.y < tr)\n" " insideInner = insideInner && (length(innerLocal - vec2(iw - tr, tr)) <= tr);\n" " // Bottom-left\n" " if (bl> 0.0 && innerLocal.x < bl && innerLocal.y > ih - bl) \n" " insideInner = insideInner && (length(innerLocal - vec2(bl, ih - bl)) <= bl);\n" " // Bottom-right\n" " if (br > 0.0 && innerLocal.x > iw - br && innerLocal.y > ih - br)\n" " insideInner = insideInner && (length(innerLocal - vec2(iw - br, ih - br)) <= br);\n" " // Discard pixels inside inner rounded rect\n" " if (insideInner && innerLocal.x >= 0.0 && innerLocal.x <= iw && innerLocal.y >= 0.0 && innerLocal.y <= ih)\n" " discard;\n" " frag = vColor;\n" " return;\n" " }\n" " // -------- Non-border rectangle or image --------\n" " if (vTexSlot < 0.0) {\n" " frag = vColor;\n" " } else {\n" " int slot = int(vTexSlot + 0.5);\n" " if (slot == 0) frag = texture(uTex0, vUV);\n" " if (slot == 1) frag = texture(uTex1, vUV);\n" " if (slot == 2) frag = texture(uTex2, vUV);\n" " if (slot == 3) frag = texture(uTex3, vUV);\n" " }\n" "}\n"; const char *GLES3_TEXT_VERTEX_SHADER = GLSL_VERSION "\n" "precision mediump float;\n" "layout(location = 0) in vec2 aPos;\n" "layout(location = 1) in vec2 aUV;\n" "layout(location = 2) in vec4 aColor;\n" "layout(location = 3) in float aTexSlot;\n" "uniform vec2 uScreen;\n" "out vec2 vUV;\n" "out vec4 vColor;\n" "out float vTexSlot;\n" "void main() {\n" " vec2 ndc = (aPos / uScreen) * 2.0 - 1.0;\n" " gl_Position = vec4(ndc * vec2(1.0, -1.0), 0.0, 1.0);\n" " vUV = aUV;\n" " vColor = aColor;\n" " vTexSlot = aTexSlot;\n" "}\n"; const char *GLES3_TEXT_FRAGMENT_SHADER = GLSL_VERSION "\n" "precision mediump float;\n" "in vec2 vUV;\n" "in vec4 vColor;\n" "in float vTexSlot;\n" "uniform sampler2D uTex0;\n" "uniform sampler2D uTex1;\n" "uniform sampler2D uTex2;\n" "uniform sampler2D uTex3;\n" "out vec4 fragColor;\n" "void main() {\n" " int slot = int(vTexSlot + 0.5);\n" " float coverage;\n" " if (slot == 0) coverage = texture(uTex0, vUV).r;\n" " if (slot == 1) coverage = texture(uTex1, vUV).r;\n" " if (slot == 2) coverage = texture(uTex2, vUV).r;\n" " if (slot == 3) coverage = texture(uTex3, vUV).r;\n" " fragColor = vec4(vColor.rgb, vColor.a * coverage);\n" "} \n"; /** * This renderer accumulates all quads and glyphs of every draw coommand * in their array, and flushes them in just 2 instanced draw calls to OpenGL */ typedef struct Gles3_Renderer { Clay_Arena clayMemory; // It is super important keep track on the performance of this renderer: uint64_t totalDrawCallsToOpenGl; float screenWidth; float screenHeight; /* Quads rendering */ GLuint quadVAO; GLuint quadVBO; GLuint quadInstanceVBO; GLuint quadShaderId; GLuint imageTextures[MAX_IMAGES]; Gles3_QuadInstanceArray quadInstanceArray; // Each instance is one quad /* Fonts rendering */ GLuint textVAO; GLuint textVBO; GLuint textShader; GLuint fontTextures[MAX_FONTS]; Gles3_GlyphVtxArray glyphVtxArray; // Instance data: every vertex is an element, // 6 elements per each instance // Text renderer is delegated to external function, which is supposed // to add glyph data based on passed render text command void (*renderTextFunction)( Clay_RenderCommand *cmd, // Will be always of CLAY_RENDER_COMMAND_TYPE_TEXT Gles3_GlyphVtxArray *accum, // 6 vertices need to be added to this array void *userData // Fonts pallete ); } Gles3_Renderer; static GLuint Gles3__CompileShader(GLenum type, const char *source) { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, NULL); glCompileShader(shader); GLint success; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { char infoLog[512]; glGetShaderInfoLog(shader, 512, NULL, infoLog); printf("ERROR::SHADER::COMPILATION_FAILED\n"); printf("SHADER SOURCE:\n%s\n", source); printf("SHADER TYPE: "); if (type == GL_VERTEX_SHADER) printf("Vertex Shader"); else if (type == GL_FRAGMENT_SHADER) printf("Fragment Shader"); else printf("Unknown"); printf("\nSHADER COMPILATION ERROR:\n%s\n", infoLog); abort(); } return shader; } GLuint Gles3__CreateShaderProgram( const char *vertexShaderSource, const char *fragmentShaderSource) { GLuint vertexShader = Gles3__CompileShader(GL_VERTEX_SHADER, vertexShaderSource); GLuint fragmentShader = Gles3__CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource); GLuint shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); glDeleteShader(vertexShader); glDeleteShader(fragmentShader); return shaderProgram; } void Gles3_Initialize(Gles3_Renderer *renderer, int maxInstances) { renderer->totalDrawCallsToOpenGl = 0; // compile shader renderer->quadShaderId = Gles3__CreateShaderProgram( GLES3_QUAD_VERTEX_SHADER, GLES3_QUAD_FRAGMENT_SHADER); glUseProgram(renderer->quadShaderId); glUniform1i(glGetUniformLocation(renderer->quadShaderId, "uTex0"), 0); glUniform1i(glGetUniformLocation(renderer->quadShaderId, "uTex1"), 1); glUniform1i(glGetUniformLocation(renderer->quadShaderId, "uTex2"), 2); glUniform1i(glGetUniformLocation(renderer->quadShaderId, "uTex3"), 3); // create unit quad VBO (0..1) const float quadVerts[8] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; glGenVertexArrays(1, &renderer->quadVAO); glBindVertexArray(renderer->quadVAO); glGenBuffers(1, &renderer->quadVBO); glBindBuffer(GL_ARRAY_BUFFER, renderer->quadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVerts), quadVerts, GL_STATIC_DRAW); // attribute 0: aPos (vec2), per-vertex glEnableVertexAttribArray(ATTR_QUAD_POS); glVertexAttribPointer(ATTR_QUAD_POS, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); glVertexAttribDivisor(ATTR_QUAD_POS, 0); // create instance buffer big enough Gles3_QuadInstanceArray *quads = &renderer->quadInstanceArray; quads->capacity = maxInstances; quads->instData = (RectInstance *)malloc(sizeof(RectInstance) * quads->capacity); quads->count = 0; glGenBuffers(1, &renderer->quadInstanceVBO); glBindBuffer(GL_ARRAY_BUFFER, renderer->quadInstanceVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(RectInstance) * quads->capacity, NULL, GL_DYNAMIC_DRAW); // set up instance attributes GLsizei stride = sizeof(RectInstance); glEnableVertexAttribArray(ATTR_QUAD_RECT); glVertexAttribPointer(ATTR_QUAD_RECT, 4, GL_FLOAT, GL_FALSE, stride, (void *)offsetof(RectInstance, x)); glVertexAttribDivisor(ATTR_QUAD_RECT, 1); glEnableVertexAttribArray(ATTR_QUAD_COLOR); glVertexAttribPointer(ATTR_QUAD_COLOR, 4, GL_FLOAT, GL_FALSE, stride, (void *)offsetof(RectInstance, r)); glVertexAttribDivisor(ATTR_QUAD_COLOR, 1); glEnableVertexAttribArray(ATTR_QUAD_UV); glVertexAttribPointer(ATTR_QUAD_UV, 4, GL_FLOAT, GL_FALSE, stride, (void *)offsetof(RectInstance, u0)); glVertexAttribDivisor(ATTR_QUAD_UV, 1); glEnableVertexAttribArray(ATTR_QUAD_RAD); glVertexAttribPointer(ATTR_QUAD_RAD, 4, GL_FLOAT, GL_FALSE, stride, (void *)offsetof(RectInstance, radiusTL)); glVertexAttribDivisor(ATTR_QUAD_RAD, 1); glEnableVertexAttribArray(ATTR_QUAD_BORDER); glVertexAttribPointer(ATTR_QUAD_BORDER, 4, GL_FLOAT, GL_FALSE, stride, (void *)offsetof(RectInstance, borderL)); glVertexAttribDivisor(ATTR_QUAD_BORDER, 1); glEnableVertexAttribArray(ATTR_QUAD_TEX); glVertexAttribPointer(ATTR_QUAD_TEX, 1, GL_FLOAT, GL_FALSE, stride, (void *)offsetof(RectInstance, texToUse)); glVertexAttribDivisor(ATTR_QUAD_TEX, 1); glBindVertexArray(1); glBindBuffer(GL_ARRAY_BUFFER, 0); // Ok now we will initialize text! Gles3_GlyphVtxArray *gVerts = &renderer->glyphVtxArray; // configure capacity gVerts->capacity = maxInstances; gVerts->count = 0; // allocate CPU-side vertex buffer: 6 vertices per glyph gVerts->instData = (GlyphVtx *)malloc(sizeof(GlyphVtx) * 6 * gVerts->capacity); if (!gVerts->instData) { fprintf(stderr, "Failed to allocate glyph_vertices\n"); gVerts->capacity = 0; } // create VAO/VBO for text rendering glGenVertexArrays(1, &renderer->textVAO); glBindVertexArray(renderer->textVAO); glGenBuffers(1, &renderer->textVBO); glBindBuffer(GL_ARRAY_BUFFER, renderer->textVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(GlyphVtx) * 6 * gVerts->capacity, NULL, GL_DYNAMIC_DRAW); GLsizei gv_stride = sizeof(GlyphVtx); glEnableVertexAttribArray(ATTR_GLYPH_POS); glVertexAttribPointer(ATTR_GLYPH_POS, 2, GL_FLOAT, GL_FALSE, gv_stride, (void *)(offsetof(GlyphVtx, x))); glEnableVertexAttribArray(ATTR_GLYPH_UV); glVertexAttribPointer(ATTR_GLYPH_UV, 2, GL_FLOAT, GL_FALSE, gv_stride, (void *)(offsetof(GlyphVtx, u))); glEnableVertexAttribArray(ATTR_GLYPH_COLOR); glVertexAttribPointer(ATTR_GLYPH_COLOR, 4, GL_FLOAT, GL_FALSE, gv_stride, (void *)(offsetof(GlyphVtx, r))); glEnableVertexAttribArray(ATTR_GLYPH_TEX); glVertexAttribPointer(ATTR_GLYPH_TEX, 1, GL_FLOAT, GL_FALSE, gv_stride, (void *)(offsetof(GlyphVtx, atlasTexUnit))); glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); renderer->textShader = Gles3__CreateShaderProgram( GLES3_TEXT_VERTEX_SHADER, GLES3_TEXT_FRAGMENT_SHADER); glUseProgram(renderer->textShader); // Link sampler uniforms in the text shader to the correct texture units. // Each uniform tells the shader which unit to read from. glUniform1i(glGetUniformLocation(renderer->textShader, "uTex0"), 0); glUniform1i(glGetUniformLocation(renderer->textShader, "uTex1"), 1); glUniform1i(glGetUniformLocation(renderer->textShader, "uTex2"), 2); glUniform1i(glGetUniformLocation(renderer->textShader, "uTex3"), 3); } void Gles3_SetRenderTextFunction( Gles3_Renderer *renderer, void (*renderTextFunction)( Clay_RenderCommand *cmd, Gles3_GlyphVtxArray *accum, void *userData), void *userData) { renderer->renderTextFunction = renderTextFunction; } void Gles3_Render( Gles3_Renderer *renderer, Clay_RenderCommandArray cmds, void *userData // eg. fonts ) { Clay_Dimensions layoutDimensions = Clay_GetCurrentContext()->layoutDimensions; renderer->screenWidth = layoutDimensions.width; renderer->screenHeight = layoutDimensions.height; Gles3_QuadInstanceArray *quads = &renderer->quadInstanceArray; Gles3_GlyphVtxArray *gVerts = &renderer->glyphVtxArray; gVerts->count = 0; for (int i = 0; i < cmds.length; i++) { Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&cmds, i); Clay_BoundingBox boundingBox = (Clay_BoundingBox){ .x = roundf(cmd->boundingBox.x), .y = roundf(cmd->boundingBox.y), .width = roundf(cmd->boundingBox.width), .height = roundf(cmd->boundingBox.height), }; bool scissorChanged = false; switch (cmd->commandType) { case CLAY_RENDER_COMMAND_TYPE_TEXT: { renderer->renderTextFunction( cmd, &renderer->glyphVtxArray, userData); break; } case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: case CLAY_RENDER_COMMAND_TYPE_IMAGE: { Clay_RectangleRenderData *config = &cmd->renderData.rectangle; Clay_Color c = config->backgroundColor; // Convert to float 0..1 float rf = c.r / 255.0f; float gf = c.g / 255.0f; float bf = c.b / 255.0f; float af = c.a / 255.0f; bool isImage = cmd->commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE; // Ensure we don't overflow the capacity if (quads->count >= quads->capacity) { printf("Clay renderer: instance overflow!\n"); break; } int idx = quads->count; RectInstance *dst = &quads->instData[idx]; dst->x = boundingBox.x; dst->y = boundingBox.y; dst->w = boundingBox.width; dst->h = boundingBox.height; if (isImage) { Gles3_ImageConfig *imgConf = (Gles3_ImageConfig *)cmd->renderData.image.imageData; dst->u0 = imgConf->u0; dst->v0 = imgConf->v0; dst->u1 = imgConf->u1; dst->v1 = imgConf->v1; dst->texToUse = (float)imgConf->textureToUse; } else { dst->u0 = dst->v0 = 0.0f; dst->u1 = dst->v1 = 1.0f; dst->texToUse = -1.0f; // This means no image, use albedo color } // colour dst->r = rf; dst->g = gf; dst->b = bf; dst->a = af; // corner radii Clay_CornerRadius r = config->cornerRadius; dst->radiusTL = r.topLeft; dst->radiusTR = r.topRight; dst->radiusBL = r.bottomLeft; dst->radiusBR = r.bottomRight; dst->borderT = 0.0f; dst->borderR = 0.0f; dst->borderB = 0.0f; dst->borderL = 0.0f; quads->count++; break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { scissorChanged = true; break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { scissorChanged = true; break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData *br = &cmd->renderData.border; float rf = br->color.r / 255.0f; float gf = br->color.g / 255.0f; float bf = br->color.b / 255.0f; float af = br->color.a / 255.0f; float x = boundingBox.x; float y = boundingBox.y; float w = boundingBox.width; float h = boundingBox.height; float top = br->width.top; float bottom = br->width.bottom; float left = br->width.left; float right = br->width.right; int idx = quads->count; RectInstance *dst = &quads->instData[idx]; dst->x = x - left; dst->y = y - top; dst->w = w + right; dst->h = h + bottom; dst->borderB = bottom; dst->borderL = left; dst->borderT = top; dst->borderR = right; // Clay borders are inset, but adding support to outset borders // Is as easy as this + some minor changes in shader too bool CLAY_BORDERS_ARE_INSET = true; if (CLAY_BORDERS_ARE_INSET) { // Normal behaviour dst->x = x; dst->y = y; dst->w = w; dst->h = h; } else { // Hypotethical behaviour, if the borders were outside dst->x = x - left; dst->y = y - top; dst->w = w + left + right; dst->h = h + top + bottom; } dst->u0 = 0.0f; dst->v0 = 0.0f; dst->u1 = 1.0f; dst->v1 = 1.0f; dst->r = rf; dst->g = gf; dst->b = bf; dst->a = af; dst->radiusTL = br->cornerRadius.topLeft; dst->radiusTR = br->cornerRadius.topRight; dst->radiusBR = br->cornerRadius.bottomRight; dst->radiusBL = br->cornerRadius.bottomLeft; dst->texToUse = -1.0f; quads->count++; break; } case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { // printf("Unhandled clay cmd: custom\n"); break; } default: { printf("Error: unhandled render command\n"); exit(1); } } // Flush draw calls if scissors about to change in this iteration if (i == cmds.length - 1 || scissorChanged) { scissorChanged = false; // Render Recatangles and Images if (quads->count > 0) { glUseProgram(renderer->quadShaderId); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, renderer->imageTextures[0]); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, renderer->imageTextures[1]); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, renderer->imageTextures[2]); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, renderer->imageTextures[3]); // set uniforms GLint locScreen = glGetUniformLocation(renderer->quadShaderId, "uScreen"); glUniform2f(locScreen, (float)renderer->screenWidth, (float)renderer->screenHeight); glBindVertexArray(renderer->quadVAO); // upload all instances at once glBindBuffer(GL_ARRAY_BUFFER, renderer->quadInstanceVBO); // rectangles are solid colour — disable atlas use glBufferSubData(GL_ARRAY_BUFFER, 0, quads->count * sizeof(RectInstance), quads->instData); // draw unit quad (4 verts) instanced glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, quads->count); renderer->totalDrawCallsToOpenGl += 1; glBindVertexArray(0); glUseProgram(0); } // Clrear instance arrays, as they were flushed to their render calls quads->count = 0; // Text rendering if (renderer->glyphVtxArray.count > 0) { glUseProgram(renderer->textShader); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, renderer->fontTextures[0]); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, renderer->fontTextures[1]); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, renderer->fontTextures[2]); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, renderer->fontTextures[3]); GLint uScreenLoc = glGetUniformLocation(renderer->textShader, "uScreen"); glUniform2f(uScreenLoc, renderer->screenWidth, renderer->screenHeight); glBindVertexArray(renderer->textVAO); glBindBuffer(GL_ARRAY_BUFFER, renderer->textVBO); glBufferSubData( GL_ARRAY_BUFFER, 0, sizeof(struct GlyphVtx) * 6 * gVerts->count, renderer->glyphVtxArray.instData); glDrawArrays(GL_TRIANGLES, 0, renderer->glyphVtxArray.count * 6); renderer->totalDrawCallsToOpenGl += 1; glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); } renderer->glyphVtxArray.count = 0; if (cmd->commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START) { Clay_BoundingBox bb = cmd->boundingBox; GLint x = (GLint)bb.x; GLint y = (GLint)(renderer->screenHeight - (bb.y + bb.height)); GLsizei w = (GLsizei)bb.width; GLsizei h = (GLsizei)bb.height; glEnable(GL_SCISSOR_TEST); glScissor(x, y, w, h); } else { glDisable(GL_SCISSOR_TEST); } } } } #endif #endif ================================================ FILE: renderers/GLES3/clay_renderer_gles3_loader_stb.c ================================================ #pragma once #include #include #include #include #include "clay_renderer_gles3.h" typedef struct LoadedImage { unsigned char *data; int width; int height; int channels; } LoadedImage; typedef struct LoadedImageInternal { LoadedImage pub; } LoadedImageInternal; static LoadedImageInternal g_imageSlot; const LoadedImage *loadImage(const char *path, bool flip) { if (!path) return NULL; stbi_set_flip_vertically_on_load(flip ? 1 : 0); int w = 0; int h = 0; int c = 0; unsigned char *data = stbi_load(path, &w, &h, &c, 0); if (!data) { // Failed g_imageSlot.pub.data = NULL; g_imageSlot.pub.width = 0; g_imageSlot.pub.height = 0; g_imageSlot.pub.channels = 0; return NULL; } g_imageSlot.pub.data = data; g_imageSlot.pub.width = w; g_imageSlot.pub.height = h; g_imageSlot.pub.channels = c; return &g_imageSlot.pub; } void freeImage(const LoadedImage *img) { if (!img || !img->data) return; // cast back to internal container stbi_image_free((void *)img->data); // reset slot g_imageSlot.pub.data = NULL; g_imageSlot.pub.width = 0; g_imageSlot.pub.height = 0; g_imageSlot.pub.channels = 0; } typedef struct Stb_FontData { float bakePxH; // font baking height (e.g. 48.0f) float ascentPx; // in baked pixels (at bake_px size) float descentPx; // usually negative (at bake_px size) int firstChar; // e.g. 32 int charCount; // e.g. 96 stbtt_bakedchar *cdata; int atlasW; int atlasH; } Stb_FontData; bool Stb_LoadFont( GLuint *textureOut, Stb_FontData *fontOut, const char *ttfPath, float bakePxH, // Height of a char in pixels int atlasW, // Width of atlas in pixels int atlasH // Height of atlas in pixels ) { fontOut->firstChar = 32; // ASCII space fontOut->charCount = 96; // 32..127 fontOut->bakePxH = bakePxH; fontOut->atlasW = atlasW; fontOut->atlasH = atlasH; // allocate baked-char array fontOut->cdata = (stbtt_bakedchar *)malloc( sizeof(stbtt_bakedchar) // Store baked info * fontOut->charCount // For each char ); if (!fontOut->cdata) { fprintf(stderr, "Cannot allocate cdata\n"); return false; } FILE *f = fopen(ttfPath, "rb"); if (!f) { fprintf(stderr, "Could not open font: %s\n", ttfPath); return false; } fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET); unsigned char *ttf_buf = (unsigned char *)malloc(sz); fread(ttf_buf, 1, sz, f); fclose(f); // temporary atlas memory unsigned char *atlas = (unsigned char *)malloc(atlasW * atlasH); memset(atlas, 0, atlasW * atlasH); // bake int res = stbtt_BakeFontBitmap( ttf_buf, // raw TTF file 0, // font index inside TTF (0 = first font) bakePxH, // pixel height of glyphs to generate atlas, // OUT: bitmap buffer (unsigned char*) atlasW, atlasH, // size of bitmap buffer fontOut->firstChar, // first character to bake (e.g., 32 = space) fontOut->charCount, // how many sequential chars to bake fontOut->cdata // OUT: array of stbtt_bakedchar ); stbtt_fontinfo fi; if (!stbtt_InitFont(&fi, ttf_buf, stbtt_GetFontOffsetForIndex(ttf_buf, 0))) { return false; } int ascent, descent, lineGap; stbtt_GetFontVMetrics(&fi, &ascent, &descent, &lineGap); // Convert the font's "font units" to pixels proportional to bakePxH size: float scaleForBake = stbtt_ScaleForPixelHeight(&fi, bakePxH); fontOut->ascentPx = ascent * scaleForBake; fontOut->descentPx = descent * scaleForBake; // this is typically negative free(ttf_buf); if (res <= 0) { fprintf(stderr, "Font baking failed\n"); free(atlas); free(fontOut->cdata); fontOut->cdata = NULL; return false; } // Creating glyphVtxArray atlas texture glGenTextures(1, textureOut); glBindTexture(GL_TEXTURE_2D, *textureOut); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, atlasW, atlasH, 0, GL_RED, GL_UNSIGNED_BYTE, atlas); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); free(atlas); return true; } static inline Clay_Dimensions Stb_MeasureText( Clay_StringSlice glyphVtxArray, Clay_TextElementConfig *config, void *userData) { Stb_FontData *fontData = (Stb_FontData *)userData; if (!fontData->cdata) { fprintf( stderr, "MeasureText cannot do anything when cdata is not baked: '%.*s' → %d x %d px\n", (int)glyphVtxArray.length, glyphVtxArray.chars, 0, 0); return (Clay_Dimensions){.width = 0, .height = 0}; } float x = 0.0f; float y = 0.0f; const char *str = glyphVtxArray.chars; int len = glyphVtxArray.length; float scale = config->fontSize / fontData->bakePxH; float letterSpacing = (float)config->letterSpacing; float lineHeight = (config->lineHeight > 0) ? (float)config->lineHeight : fontData->bakePxH; for (int i = 0; i < len; i++) { unsigned char c = str[i]; if (c < fontData->firstChar // before range || c >= fontData->firstChar + fontData->charCount // after range ) { // Unsupported char: treat as space fprintf(stderr, "illegal char %d\n", (int)c); x += fontData->bakePxH * 0.25f; continue; } stbtt_bakedchar *b = &fontData->cdata[c - fontData->firstChar]; // horizontal advance while moving along word characters x += b->xadvance * scale + letterSpacing; } float ascent = fontData->ascentPx * scale; float descent = fontData->descentPx * scale; // negative float lineH = (ascent - descent); // total line height in pixels (at requested fontSize) return (Clay_Dimensions){ .width = x, .height = y + lineH, }; } static inline void Stb_RenderText( Clay_RenderCommand *cmd, Gles3_GlyphVtxArray *glyphVtxArray, void *userData) { const Clay_TextRenderData *tr = &cmd->renderData.text; float cr = tr->textColor.r / 255.0f; float cg = tr->textColor.g / 255.0f; float cb = tr->textColor.b / 255.0f; float ca = tr->textColor.a / 255.0f; float fontToUse = (float)tr->fontId; Stb_FontData *fontArray = (Stb_FontData *)userData; Stb_FontData *stbFontData = &fontArray[tr->fontId]; if (!stbFontData->cdata) return; Clay_StringSlice ss = tr->stringContents; const char *txt = ss.chars; int len = (int)ss.length; float scale = tr->fontSize / stbFontData->bakePxH; float ascent = stbFontData->ascentPx * scale; // pixels above baseline float x = cmd->boundingBox.x; float y = cmd->boundingBox.y + ascent; // baseline (note: no descent) for (int i = 0; i < len; i++) { char ch = txt[i]; int idx = ch - stbFontData->firstChar; if (idx < 0 || idx >= stbFontData->charCount) { continue; } stbtt_bakedchar *bc = &stbFontData->cdata[idx]; float gw = (float)(bc->x1 - bc->x0); // glyph width in atlas pixels float gh = (float)(bc->y1 - bc->y0); // glyph height float sw = gw * scale; // scaled width on screen float sh = gh * scale; // scaled height float ox = bc->xoff * scale; // baseline offset float oy = bc->yoff * scale; // top-left corner on screen (pixel coords) float x0 = x + ox; float y0 = y + oy; float x1 = x0 + sw; float y1 = y0 + sh; // atlas size (you can make it configurable later) float atlasW = stbFontData->atlasW; float atlasH = stbFontData->atlasH; float u0 = bc->x0 / atlasW; float v0 = bc->y0 / atlasH; float u1 = bc->x1 / atlasW; float v1 = bc->y1 / atlasH; // append 6 vertices (two triangles) to your buffer GlyphVtx *v = &glyphVtxArray->instData[glyphVtxArray->count * 6]; v[0] = (GlyphVtx){x0, y0, u0, v0, cr, cg, cb, ca, fontToUse}; v[1] = (GlyphVtx){x1, y0, u1, v0, cr, cg, cb, ca, fontToUse}; v[2] = (GlyphVtx){x0, y1, u0, v1, cr, cg, cb, ca, fontToUse}; v[3] = (GlyphVtx){x0, y1, u0, v1, cr, cg, cb, ca, fontToUse}; v[4] = (GlyphVtx){x1, y0, u1, v0, cr, cg, cb, ca, fontToUse}; v[5] = (GlyphVtx){x1, y1, u1, v1, cr, cg, cb, ca, fontToUse}; // advance pen by baked xadvance + letter spacing x += (bc->xadvance * scale) + tr->letterSpacing; // prevent buffer overrun if (glyphVtxArray->count >= glyphVtxArray->capacity) { break; } glyphVtxArray->count++; } } bool Stb_LoadImage(GLuint *textureOut, const char *path) { const LoadedImage *li = loadImage(path, false); if (!li || !li->data) { fprintf(stderr, "Failed to load texture at: %s\n", path); return false; } glGenTextures(1, textureOut); glBindTexture(GL_TEXTURE_2D, *textureOut); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); GLenum format = (li->channels == 4) ? GL_RGBA : GL_RGB; glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D( GL_TEXTURE_2D, // target 0, // level format, // internal format int li->width, li->height, 0, // border format, // format, GLEnum GL_UNSIGNED_BYTE, // Type li->data // pixels ); glGenerateMipmap(GL_TEXTURE_2D); freeImage(li); return true; } ================================================ FILE: renderers/SDL2/README ================================================ Note: on Mac OSX, SDL2 for some reason decides to automatically disable momentum scrolling on macbook trackpads. You can re enable it in objective C using: ```C [[NSUserDefaults standardUserDefaults] setBool: YES forKey: @"AppleMomentumScrollSupported"]; ``` ================================================ FILE: renderers/SDL2/clay_renderer_SDL2.c ================================================ #include "../../clay.h" #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159 #endif #define CLAY_COLOR_TO_SDL_COLOR_ARGS(color) color.r, color.g, color.b, color.a typedef struct { uint32_t fontId; TTF_Font *font; } SDL2_Font; static Clay_Dimensions SDL2_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { SDL2_Font *fonts = (SDL2_Font*)userData; TTF_Font *font = fonts[config->fontId].font; TTF_SetFontSize(font, config->fontSize); char *chars = (char *)calloc(text.length + 1, 1); memcpy(chars, text.chars, text.length); int width = 0; int height = 0; if (TTF_SizeUTF8(font, chars, &width, &height) < 0) { fprintf(stderr, "Error: could not measure text: %s\n", TTF_GetError()); exit(1); } free(chars); return (Clay_Dimensions) { .width = (float)width, .height = (float)height, }; } /* Global for convenience. Even in 4K this is enough for smooth curves (low radius or rect size coupled with * no AA or low resolution might make it appear as jagged curves) */ static int NUM_CIRCLE_SEGMENTS = 16; //all rendering is performed by a single SDL call, avoiding multiple RenderRect + plumbing choice for circles. static void SDL_RenderFillRoundedRect(SDL_Renderer* renderer, const SDL_FRect rect, const float cornerRadius, const Clay_Color _color) { const SDL_Color color = (SDL_Color) { .r = (Uint8)_color.r, .g = (Uint8)_color.g, .b = (Uint8)_color.b, .a = (Uint8)_color.a, }; int indexCount = 0, vertexCount = 0; const float maxRadius = SDL_min(rect.w, rect.h) / 2.0f; const float clampedRadius = SDL_min(cornerRadius, maxRadius); const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int)clampedRadius * 0.5f); SDL_Vertex vertices[512]; int indices[512]; //define center rectangle vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + clampedRadius}, color, {0, 0} }; //0 center TL vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + clampedRadius}, color, {1, 0} }; //1 center TR vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //2 center BR vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //3 center BL indices[indexCount++] = 0; indices[indexCount++] = 1; indices[indexCount++] = 3; indices[indexCount++] = 1; indices[indexCount++] = 2; indices[indexCount++] = 3; //define rounded corners as triangle fans const float step = (M_PI / 2) / numCircleSegments; for (int i = 0; i < numCircleSegments; i++) { const float angle1 = (float)i * step; const float angle2 = ((float)i + 1.0f) * step; for (int j = 0; j < 4; j++) { // Iterate over four corners float cx, cy, signX, signY; switch (j) { case 0: cx = rect.x + clampedRadius; cy = rect.y + clampedRadius; signX = -1; signY = -1; break; // Top-left case 1: cx = rect.x + rect.w - clampedRadius; cy = rect.y + clampedRadius; signX = 1; signY = -1; break; // Top-right case 2: cx = rect.x + rect.w - clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = 1; signY = 1; break; // Bottom-right case 3: cx = rect.x + clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = -1; signY = 1; break; // Bottom-left default: return; } vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle1) * clampedRadius * signX, cy + SDL_sinf(angle1) * clampedRadius * signY}, color, {0, 0} }; vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle2) * clampedRadius * signX, cy + SDL_sinf(angle2) * clampedRadius * signY}, color, {0, 0} }; indices[indexCount++] = j; // Connect to corresponding central rectangle vertex indices[indexCount++] = vertexCount - 2; indices[indexCount++] = vertexCount - 1; } } //Define edge rectangles // Top edge vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y}, color, {0, 0} }; //TL vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y}, color, {1, 0} }; //TR indices[indexCount++] = 0; indices[indexCount++] = vertexCount - 2; //TL indices[indexCount++] = vertexCount - 1; //TR indices[indexCount++] = 1; indices[indexCount++] = 0; indices[indexCount++] = vertexCount - 1; //TR // Right edge vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + clampedRadius}, color, {1, 0} }; //RT vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //RB indices[indexCount++] = 1; indices[indexCount++] = vertexCount - 2; //RT indices[indexCount++] = vertexCount - 1; //RB indices[indexCount++] = 2; indices[indexCount++] = 1; indices[indexCount++] = vertexCount - 1; //RB // Bottom edge vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h}, color, {1, 1} }; //BR vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h}, color, {0, 1} }; //BL indices[indexCount++] = 2; indices[indexCount++] = vertexCount - 2; //BR indices[indexCount++] = vertexCount - 1; //BL indices[indexCount++] = 3; indices[indexCount++] = 2; indices[indexCount++] = vertexCount - 1; //BL // Left edge vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //LB vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + clampedRadius}, color, {0, 0} }; //LT indices[indexCount++] = 3; indices[indexCount++] = vertexCount - 2; //LB indices[indexCount++] = vertexCount - 1; //LT indices[indexCount++] = 0; indices[indexCount++] = 3; indices[indexCount++] = vertexCount - 1; //LT // Render everything SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount); } //all rendering is performed by a single SDL call, using twi sets of arcing triangles, inner and outer, that fit together; along with two tringles to fill the end gaps. static void SDL_RenderCornerBorder(SDL_Renderer *renderer, Clay_BoundingBox* boundingBox, Clay_BorderRenderData* config, int cornerIndex, Clay_Color _color){ ///////////////////////////////// //The arc is constructed of outer triangles and inner triangles (if needed). //First three vertices are first outer triangle's vertices //Each two vertices after that are the inner-middle and second-outer vertex of //each outer triangle after the first, because there first-outer vertex is equal to the //second-outer vertex of the previous triangle. Indices set accordingly. //The final two vertices are the missing vertices for the first and last inner triangles (if needed) //Everything is in clockwise order (CW). ///////////////////////////////// const SDL_Color color = (SDL_Color) { .r = (Uint8)_color.r, .g = (Uint8)_color.g, .b = (Uint8)_color.b, .a = (Uint8)_color.a, }; float centerX, centerY, outerRadius, clampedRadius, startAngle, borderWidth; const float maxRadius = SDL_min(boundingBox->width, boundingBox->height) / 2.0f; SDL_Vertex vertices[512]; int indices[512]; int indexCount = 0, vertexCount = 0; switch (cornerIndex) { case(0): startAngle = M_PI; outerRadius = SDL_min(config->cornerRadius.topLeft, maxRadius); centerX = boundingBox->x + outerRadius; centerY = boundingBox->y + outerRadius; borderWidth = config->width.top; break; case(1): startAngle = 3*M_PI/2; outerRadius = SDL_min(config->cornerRadius.topRight, maxRadius); centerX = boundingBox->x + boundingBox->width - outerRadius; centerY = boundingBox->y + outerRadius; borderWidth = config->width.top; break; case(2): startAngle = 0; outerRadius = SDL_min(config->cornerRadius.bottomRight, maxRadius); centerX = boundingBox->x + boundingBox->width - outerRadius; centerY = boundingBox->y + boundingBox->height - outerRadius; borderWidth = config->width.bottom; break; case(3): startAngle = M_PI/2; outerRadius = SDL_min(config->cornerRadius.bottomLeft, maxRadius); centerX = boundingBox->x + outerRadius; centerY = boundingBox->y + boundingBox->height - outerRadius; borderWidth = config->width.bottom; break; default: break; } const float innerRadius = outerRadius - borderWidth; const int minNumOuterTriangles = NUM_CIRCLE_SEGMENTS; const int numOuterTriangles = SDL_max(minNumOuterTriangles, ceilf(outerRadius * 0.5f)); const float angleStep = M_PI / (2.0*(float)numOuterTriangles); //outer triangles, in CW order for (int i = 0; i < numOuterTriangles; i++) { float angle1 = startAngle + i*angleStep; //first-outer vertex angle float angle2 = startAngle + ((float)i + 0.5) * angleStep; //inner-middle vertex angle float angle3 = startAngle + (i+1)*angleStep; // second-outer vertex angle if( i == 0){ //first outer triangle vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(angle1) * outerRadius, centerY + SDL_sinf(angle1) * outerRadius}, color, {0, 0} }; //vertex index = 0 } indices[indexCount++] = vertexCount - 1; //will be second-outer vertex of last outer triangle if not first outer triangle. vertices[vertexCount++] = (innerRadius > 0)? (SDL_Vertex){ {centerX + SDL_cosf(angle2) * (innerRadius), centerY + SDL_sinf(angle2) * (innerRadius)}, color, {0, 0}}: (SDL_Vertex){ {centerX, centerY }, color, {0, 0}}; indices[indexCount++] = vertexCount - 1; vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(angle3) * outerRadius, centerY + SDL_sinf(angle3) * outerRadius}, color, {0, 0} }; indices[indexCount++] = vertexCount - 1; } if(innerRadius > 0){ // inner triangles in CW order (except the first and last) for (int i = 0; i < numOuterTriangles - 1; i++){ //skip the last outer triangle if(i==0){ //first outer triangle -> second inner triangle indices[indexCount++] = 1; //inner-middle vertex of first outer triangle indices[indexCount++] = 2; //second-outer vertex of first outer triangle indices[indexCount++] = 3; //innder-middle vertex of second-outer triangle }else{ int baseIndex = 3; //skip first outer triangle indices[indexCount++] = baseIndex + (i-1)*2; // inner-middle vertex of current outer triangle indices[indexCount++] = baseIndex + (i-1)*2 + 1; // second-outer vertex of current outer triangle indices[indexCount++] = baseIndex + (i-1)*2 + 2; // inner-middle vertex of next outer triangle } } float endAngle = startAngle + M_PI/2.0; //last inner triangle indices[indexCount++] = vertexCount - 2; //inner-middle vertex of last outer triangle indices[indexCount++] = vertexCount - 1; //second-outer vertex of last outer triangle vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(endAngle) * innerRadius, centerY + SDL_sinf(endAngle) * innerRadius}, color, {0, 0} }; //missing vertex indices[indexCount++] = vertexCount - 1; // //first inner triangle indices[indexCount++] = 0; //first-outer vertex of first outer triangle indices[indexCount++] = 1; //inner-middle vertex of first outer triangle vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(startAngle) * innerRadius, centerY + SDL_sinf(startAngle) * innerRadius}, color, {0, 0} }; //missing vertex indices[indexCount++] = vertexCount - 1; } SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount); } SDL_Rect currentClippingRectangle; static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands, SDL2_Font *fonts) { for (uint32_t i = 0; i < renderCommands.length; i++) { Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i); Clay_BoundingBox boundingBox = renderCommand->boundingBox; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; Clay_Color color = config->backgroundColor; SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); SDL_FRect rect = (SDL_FRect) { .x = boundingBox.x, .y = boundingBox.y, .w = boundingBox.width, .h = boundingBox.height, }; if (config->cornerRadius.topLeft > 0) { SDL_RenderFillRoundedRect(renderer, rect, config->cornerRadius.topLeft, color); } else { SDL_RenderFillRectF(renderer, &rect); } break; } case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData *config = &renderCommand->renderData.text; char *cloned = (char *)calloc(config->stringContents.length + 1, 1); memcpy(cloned, config->stringContents.chars, config->stringContents.length); TTF_Font* font = fonts[config->fontId].font; TTF_SetFontSize(font, config->fontSize); SDL_Surface *surface = TTF_RenderUTF8_Blended(font, cloned, (SDL_Color) { .r = (Uint8)config->textColor.r, .g = (Uint8)config->textColor.g, .b = (Uint8)config->textColor.b, .a = (Uint8)config->textColor.a, }); SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_Rect destination = (SDL_Rect){ .x = boundingBox.x, .y = boundingBox.y, .w = boundingBox.width, .h = boundingBox.height, }; SDL_RenderCopy(renderer, texture, NULL, &destination); SDL_DestroyTexture(texture); SDL_FreeSurface(surface); free(cloned); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { currentClippingRectangle = (SDL_Rect) { .x = boundingBox.x, .y = boundingBox.y, .w = boundingBox.width, .h = boundingBox.height, }; SDL_RenderSetClipRect(renderer, ¤tClippingRectangle); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { SDL_RenderSetClipRect(renderer, NULL); break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { Clay_ImageRenderData *config = &renderCommand->renderData.image; SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, config->imageData); SDL_Rect destination = (SDL_Rect){ .x = boundingBox.x, .y = boundingBox.y, .w = boundingBox.width, .h = boundingBox.height, }; SDL_RenderCopy(renderer, texture, NULL, &destination); SDL_DestroyTexture(texture); break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData *config = &renderCommand->renderData.border; SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color)); if(boundingBox.width > 0 & boundingBox.height > 0){ const float maxRadius = SDL_min(boundingBox.width, boundingBox.height) / 2.0f; if (config->width.left > 0) { const float clampedRadiusTop = SDL_min((float)config->cornerRadius.topLeft, maxRadius); const float clampedRadiusBottom = SDL_min((float)config->cornerRadius.bottomLeft, maxRadius); SDL_FRect rect = { boundingBox.x, boundingBox.y + clampedRadiusTop, (float)config->width.left, (float)boundingBox.height - clampedRadiusTop - clampedRadiusBottom }; SDL_RenderFillRectF(renderer, &rect); } if (config->width.right > 0) { const float clampedRadiusTop = SDL_min((float)config->cornerRadius.topRight, maxRadius); const float clampedRadiusBottom = SDL_min((float)config->cornerRadius.bottomRight, maxRadius); SDL_FRect rect = { boundingBox.x + boundingBox.width - config->width.right, boundingBox.y + clampedRadiusTop, (float)config->width.right, (float)boundingBox.height - clampedRadiusTop - clampedRadiusBottom }; SDL_RenderFillRectF(renderer, &rect); } if (config->width.top > 0) { const float clampedRadiusLeft = SDL_min((float)config->cornerRadius.topLeft, maxRadius); const float clampedRadiusRight = SDL_min((float)config->cornerRadius.topRight, maxRadius); SDL_FRect rect = { boundingBox.x + clampedRadiusLeft, boundingBox.y, boundingBox.width - clampedRadiusLeft - clampedRadiusRight, (float)config->width.top }; SDL_RenderFillRectF(renderer, &rect); } if (config->width.bottom > 0) { const float clampedRadiusLeft = SDL_min((float)config->cornerRadius.bottomLeft, maxRadius); const float clampedRadiusRight = SDL_min((float)config->cornerRadius.bottomRight, maxRadius); SDL_FRect rect = { boundingBox.x + clampedRadiusLeft, boundingBox.y + boundingBox.height - config->width.bottom, boundingBox.width - clampedRadiusLeft - clampedRadiusRight, (float)config->width.bottom }; SDL_RenderFillRectF(renderer, &rect); } //corner index: 0->3 topLeft -> CW -> bottonLeft if (config->width.top > 0 & config->cornerRadius.topLeft > 0) { SDL_RenderCornerBorder(renderer, &boundingBox, config, 0, config->color); } if (config->width.top > 0 & config->cornerRadius.topRight> 0) { SDL_RenderCornerBorder(renderer, &boundingBox, config, 1, config->color); } if (config->width.bottom > 0 & config->cornerRadius.bottomRight > 0) { SDL_RenderCornerBorder(renderer, &boundingBox, config, 2, config->color); } if (config->width.bottom > 0 & config->cornerRadius.bottomLeft > 0) { SDL_RenderCornerBorder(renderer, &boundingBox, config, 3, config->color); } } break; } default: { fprintf(stderr, "Error: unhandled render command: %d\n", renderCommand->commandType); exit(1); } } } } ================================================ FILE: renderers/SDL3/clay_renderer_SDL3.c ================================================ #include "../../clay.h" #include #include #include #include typedef struct { SDL_Renderer *renderer; TTF_TextEngine *textEngine; TTF_Font **fonts; } Clay_SDL3RendererData; /* Global for convenience. Even in 4K this is enough for smooth curves (low radius or rect size coupled with * no AA or low resolution might make it appear as jagged curves) */ static int NUM_CIRCLE_SEGMENTS = 16; //all rendering is performed by a single SDL call, avoiding multiple RenderRect + plumbing choice for circles. static void SDL_Clay_RenderFillRoundedRect(Clay_SDL3RendererData *rendererData, const SDL_FRect rect, const float cornerRadius, const Clay_Color _color) { const SDL_FColor color = { _color.r/255, _color.g/255, _color.b/255, _color.a/255 }; int indexCount = 0, vertexCount = 0; const float minRadius = SDL_min(rect.w, rect.h) / 2.0f; const float clampedRadius = SDL_min(cornerRadius, minRadius); const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int) clampedRadius * 0.5f); int totalVertices = 4 + (4 * (numCircleSegments * 2)) + 2*4; int totalIndices = 6 + (4 * (numCircleSegments * 3)) + 6*4; SDL_Vertex vertices[totalVertices]; int indices[totalIndices]; //define center rectangle vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + clampedRadius}, color, {0, 0} }; //0 center TL vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + clampedRadius}, color, {1, 0} }; //1 center TR vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //2 center BR vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //3 center BL indices[indexCount++] = 0; indices[indexCount++] = 1; indices[indexCount++] = 3; indices[indexCount++] = 1; indices[indexCount++] = 2; indices[indexCount++] = 3; //define rounded corners as triangle fans const float step = (SDL_PI_F/2) / numCircleSegments; for (int i = 0; i < numCircleSegments; i++) { const float angle1 = (float)i * step; const float angle2 = ((float)i + 1.0f) * step; for (int j = 0; j < 4; j++) { // Iterate over four corners float cx, cy, signX, signY; switch (j) { case 0: cx = rect.x + clampedRadius; cy = rect.y + clampedRadius; signX = -1; signY = -1; break; // Top-left case 1: cx = rect.x + rect.w - clampedRadius; cy = rect.y + clampedRadius; signX = 1; signY = -1; break; // Top-right case 2: cx = rect.x + rect.w - clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = 1; signY = 1; break; // Bottom-right case 3: cx = rect.x + clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = -1; signY = 1; break; // Bottom-left default: return; } vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle1) * clampedRadius * signX, cy + SDL_sinf(angle1) * clampedRadius * signY}, color, {0, 0} }; vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle2) * clampedRadius * signX, cy + SDL_sinf(angle2) * clampedRadius * signY}, color, {0, 0} }; indices[indexCount++] = j; // Connect to corresponding central rectangle vertex indices[indexCount++] = vertexCount - 2; indices[indexCount++] = vertexCount - 1; } } //Define edge rectangles // Top edge vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y}, color, {0, 0} }; //TL vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y}, color, {1, 0} }; //TR indices[indexCount++] = 0; indices[indexCount++] = vertexCount - 2; //TL indices[indexCount++] = vertexCount - 1; //TR indices[indexCount++] = 1; indices[indexCount++] = 0; indices[indexCount++] = vertexCount - 1; //TR // Right edge vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + clampedRadius}, color, {1, 0} }; //RT vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //RB indices[indexCount++] = 1; indices[indexCount++] = vertexCount - 2; //RT indices[indexCount++] = vertexCount - 1; //RB indices[indexCount++] = 2; indices[indexCount++] = 1; indices[indexCount++] = vertexCount - 1; //RB // Bottom edge vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h}, color, {1, 1} }; //BR vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h}, color, {0, 1} }; //BL indices[indexCount++] = 2; indices[indexCount++] = vertexCount - 2; //BR indices[indexCount++] = vertexCount - 1; //BL indices[indexCount++] = 3; indices[indexCount++] = 2; indices[indexCount++] = vertexCount - 1; //BL // Left edge vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //LB vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + clampedRadius}, color, {0, 0} }; //LT indices[indexCount++] = 3; indices[indexCount++] = vertexCount - 2; //LB indices[indexCount++] = vertexCount - 1; //LT indices[indexCount++] = 0; indices[indexCount++] = 3; indices[indexCount++] = vertexCount - 1; //LT // Render everything SDL_RenderGeometry(rendererData->renderer, NULL, vertices, vertexCount, indices, indexCount); } static void SDL_Clay_RenderArc(Clay_SDL3RendererData *rendererData, const SDL_FPoint center, const float radius, const float startAngle, const float endAngle, const float thickness, const Clay_Color color) { SDL_SetRenderDrawColor(rendererData->renderer, color.r, color.g, color.b, color.a); const float radStart = startAngle * (SDL_PI_F / 180.0f); const float radEnd = endAngle * (SDL_PI_F / 180.0f); const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int)(radius * 1.5f)); //increase circle segments for larger circles, 1.5 is arbitrary. const float angleStep = (radEnd - radStart) / (float)numCircleSegments; const float thicknessStep = 0.4f; //arbitrary value to avoid overlapping lines. Changing THICKNESS_STEP or numCircleSegments might cause artifacts. for (float t = thicknessStep; t < thickness - thicknessStep; t += thicknessStep) { SDL_FPoint points[numCircleSegments + 1]; const float clampedRadius = SDL_max(radius - t, 1.0f); for (int i = 0; i <= numCircleSegments; i++) { const float angle = radStart + i * angleStep; points[i] = (SDL_FPoint){ SDL_roundf(center.x + SDL_cosf(angle) * clampedRadius), SDL_roundf(center.y + SDL_sinf(angle) * clampedRadius) }; } SDL_RenderLines(rendererData->renderer, points, numCircleSegments + 1); } } SDL_Rect currentClippingRectangle; static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Clay_RenderCommandArray *rcommands) { for (size_t i = 0; i < rcommands->length; i++) { Clay_RenderCommand *rcmd = Clay_RenderCommandArray_Get(rcommands, i); const Clay_BoundingBox bounding_box = rcmd->boundingBox; const SDL_FRect rect = { (int)bounding_box.x, (int)bounding_box.y, (int)bounding_box.width, (int)bounding_box.height }; switch (rcmd->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData *config = &rcmd->renderData.rectangle; SDL_SetRenderDrawBlendMode(rendererData->renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(rendererData->renderer, config->backgroundColor.r, config->backgroundColor.g, config->backgroundColor.b, config->backgroundColor.a); if (config->cornerRadius.topLeft > 0) { SDL_Clay_RenderFillRoundedRect(rendererData, rect, config->cornerRadius.topLeft, config->backgroundColor); } else { SDL_RenderFillRect(rendererData->renderer, &rect); } } break; case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData *config = &rcmd->renderData.text; TTF_Font *font = rendererData->fonts[config->fontId]; TTF_SetFontSize(font, config->fontSize); TTF_Text *text = TTF_CreateText(rendererData->textEngine, font, config->stringContents.chars, config->stringContents.length); TTF_SetTextColor(text, config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a); TTF_DrawRendererText(text, rect.x, rect.y); TTF_DestroyText(text); } break; case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData *config = &rcmd->renderData.border; const float minRadius = SDL_min(rect.w, rect.h) / 2.0f; const Clay_CornerRadius clampedRadii = { .topLeft = SDL_min(config->cornerRadius.topLeft, minRadius), .topRight = SDL_min(config->cornerRadius.topRight, minRadius), .bottomLeft = SDL_min(config->cornerRadius.bottomLeft, minRadius), .bottomRight = SDL_min(config->cornerRadius.bottomRight, minRadius) }; //edges SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a); if (config->width.left > 0) { const float starting_y = rect.y + clampedRadii.topLeft; const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft; SDL_FRect line = { rect.x - 1, starting_y, config->width.left, length }; SDL_RenderFillRect(rendererData->renderer, &line); } if (config->width.right > 0) { const float starting_x = rect.x + rect.w - (float)config->width.right + 1; const float starting_y = rect.y + clampedRadii.topRight; const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight; SDL_FRect line = { starting_x, starting_y, config->width.right, length }; SDL_RenderFillRect(rendererData->renderer, &line); } if (config->width.top > 0) { const float starting_x = rect.x + clampedRadii.topLeft; const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight; SDL_FRect line = { starting_x, rect.y - 1, length, config->width.top }; SDL_RenderFillRect(rendererData->renderer, &line); } if (config->width.bottom > 0) { const float starting_x = rect.x + clampedRadii.bottomLeft; const float starting_y = rect.y + rect.h - (float)config->width.bottom + 1; const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight; SDL_FRect line = { starting_x, starting_y, length, config->width.bottom }; SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a); SDL_RenderFillRect(rendererData->renderer, &line); } //corners if (config->cornerRadius.topLeft > 0) { const float centerX = rect.x + clampedRadii.topLeft -1; const float centerY = rect.y + clampedRadii.topLeft - 1; SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft, 180.0f, 270.0f, config->width.top, config->color); } if (config->cornerRadius.topRight > 0) { const float centerX = rect.x + rect.w - clampedRadii.topRight; const float centerY = rect.y + clampedRadii.topRight - 1; SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight, 270.0f, 360.0f, config->width.top, config->color); } if (config->cornerRadius.bottomLeft > 0) { const float centerX = rect.x + clampedRadii.bottomLeft -1; const float centerY = rect.y + rect.h - clampedRadii.bottomLeft; SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft, 90.0f, 180.0f, config->width.bottom, config->color); } if (config->cornerRadius.bottomRight > 0) { const float centerX = rect.x + rect.w - clampedRadii.bottomRight; const float centerY = rect.y + rect.h - clampedRadii.bottomRight; SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight, 0.0f, 90.0f, config->width.bottom, config->color); } } break; case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { Clay_BoundingBox boundingBox = rcmd->boundingBox; currentClippingRectangle = (SDL_Rect) { .x = boundingBox.x, .y = boundingBox.y, .w = boundingBox.width, .h = boundingBox.height, }; SDL_SetRenderClipRect(rendererData->renderer, ¤tClippingRectangle); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { SDL_SetRenderClipRect(rendererData->renderer, NULL); break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { SDL_Texture *texture = (SDL_Texture *)rcmd->renderData.image.imageData; const SDL_FRect dest = { rect.x, rect.y, rect.w, rect.h }; SDL_RenderTexture(rendererData->renderer, texture, NULL, &dest); break; } default: SDL_Log("Unknown render command type: %d", rcmd->commandType); } } } ================================================ FILE: renderers/cairo/clay_renderer_cairo.c ================================================ // Copyright (c) 2024 Justin Andreas Lacoste (@27justin) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the // use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software in a // product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // // SPDX-License-Identifier: Zlib #include #include #include #include #define CLAY_IMPLEMENTATION #include "../../clay.h" #include //////////////////////////////// // // Public API // // Initialize the internal cairo pointer with the user provided instance. // This is REQUIRED before calling Clay_Cairo_Render. void Clay_Cairo_Initialize(cairo_t *cairo); // Render the command queue to the `cairo_t*` instance you called // `Clay_Cairo_Initialize` on. void Clay_Cairo_Render(Clay_RenderCommandArray commands, char** fonts); //////////////////////////////// //////////////////////////////// // Convencience macros // #define CLAY_TO_CAIRO(color) color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0 #define DEG2RAD(degrees) (degrees * ( M_PI / 180.0 ) ) //////////////////////////////// //////////////////////////////// // Implementation // // Cairo instance static cairo_t *Clay__Cairo = NULL; // Return a null-terminated copy of Clay_String `str`. // Callee is required to free. static inline char *Clay_Cairo__NullTerminate(Clay_String *str) { char *copy = (char*) malloc(str->length + 1); if (!copy) { fprintf(stderr, "Memory allocation failed\n"); return NULL; } memcpy(copy, str->chars, str->length); copy[str->length] = '\0'; return copy; } // Measure text using cairo's *toy* text API. static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_StringSlice str, Clay_TextElementConfig *config, void *userData) { // Edge case: Clay computes the width of a whitespace character // once. Cairo does not factor in whitespaces when computing text // extents, this edge-case serves as a short-circuit to introduce // (somewhat) sensible values into Clay. char** fonts = (char**)userData; if(str.length == 1 && str.chars[0] == ' ') { cairo_text_extents_t te; cairo_text_extents(Clay__Cairo, " ", &te); return (Clay_Dimensions) { // The multiplication here follows no real logic, just // brute-forcing it until the text boundaries look // okay-ish. You should probably rather use a proper text // shaping engine like HarfBuzz or Pango. .width = ((float) te.x_advance) * 1.9f, .height = (float) config->fontSize }; } // Ensure string is null-terminated for Cairo Clay_String toTerminate = (Clay_String){ .chars = str.chars, .length = str.length, .isStaticallyAllocated = false }; char *text = Clay_Cairo__NullTerminate(&toTerminate); char *font_family = fonts[config->fontId]; // Save and reset the Cairo context to avoid unwanted transformations cairo_save(Clay__Cairo); cairo_identity_matrix(Clay__Cairo); // Set font properties cairo_select_font_face(Clay__Cairo, font_family, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(Clay__Cairo, config->fontSize); // Use glyph extents for better precision cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(Clay__Cairo); if (!scaled_font) { fprintf(stderr, "Failed to get scaled font\n"); cairo_restore(Clay__Cairo); free(text); return (Clay_Dimensions){0, 0}; } cairo_glyph_t *glyphs = NULL; int num_glyphs = 0; cairo_status_t status = cairo_scaled_font_text_to_glyphs( scaled_font, 0, 0, text, -1, &glyphs, &num_glyphs, NULL, NULL, NULL ); if (status != CAIRO_STATUS_SUCCESS || !glyphs || num_glyphs == 0) { fprintf(stderr, "Failed to generate glyphs: %s\n", cairo_status_to_string(status)); cairo_restore(Clay__Cairo); free(text); return (Clay_Dimensions){0, 0}; } // Measure the glyph extents cairo_text_extents_t glyph_extents; cairo_glyph_extents(Clay__Cairo, glyphs, num_glyphs, &glyph_extents); // Clean up glyphs cairo_glyph_free(glyphs); // Restore the Cairo context cairo_restore(Clay__Cairo); // Free temporary strings free(text); // Return dimensions return (Clay_Dimensions){ .width = (float) glyph_extents.width, .height = (float) glyph_extents.height }; } void Clay_Cairo_Initialize(cairo_t *cairo) { Clay__Cairo = cairo; } // Internally used to copy images onto our document/active workspace. void Clay_Cairo__Blit_Surface(cairo_surface_t *src_surface, cairo_surface_t *dest_surface, double x, double y, double scale_x, double scale_y) { // Create a cairo context for the destination surface cairo_t *cr = cairo_create(dest_surface); // Save the context's state cairo_save(cr); // Apply translation to position the source at (x, y) cairo_translate(cr, x, y); // Apply scaling to the context cairo_scale(cr, scale_x, scale_y); // Set the source surface at (0, 0) after applying transformations cairo_set_source_surface(cr, src_surface, 0, 0); // Paint the scaled source surface onto the destination surface cairo_paint(cr); // Restore the context's state to remove transformations cairo_restore(cr); // Clean up cairo_destroy(cr); } void Clay_Cairo_Render(Clay_RenderCommandArray commands, char** fonts) { cairo_t *cr = Clay__Cairo; for(size_t i = 0; i < commands.length; i++) { Clay_RenderCommand *command = Clay_RenderCommandArray_Get(&commands, i); switch(command->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData *config = &command->renderData.rectangle; Clay_BoundingBox bb = command->boundingBox; cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->backgroundColor)); cairo_new_sub_path(cr); cairo_arc(cr, bb.x + config->cornerRadius.topLeft, bb.y + config->cornerRadius.topLeft, config->cornerRadius.topLeft, M_PI, 3 * M_PI / 2); // 180° to 270° cairo_arc(cr, bb.x + bb.width - config->cornerRadius.topRight, bb.y + config->cornerRadius.topRight, config->cornerRadius.topRight, 3 * M_PI / 2, 2 * M_PI); // 270° to 360° cairo_arc(cr, bb.x + bb.width - config->cornerRadius.bottomRight, bb.y + bb.height - config->cornerRadius.bottomRight, config->cornerRadius.bottomRight, 0, M_PI / 2); // 0° to 90° cairo_arc(cr, bb.x + config->cornerRadius.bottomLeft, bb.y + bb.height - config->cornerRadius.bottomLeft, config->cornerRadius.bottomLeft, M_PI / 2, M_PI); // 90° to 180° cairo_close_path(cr); cairo_fill(cr); break; } case CLAY_RENDER_COMMAND_TYPE_TEXT: { // Cairo expects null terminated strings, we need to clone // to temporarily introduce one. Clay_TextRenderData *config = &command->renderData.text; Clay_String toTerminate = (Clay_String){ .chars = config->stringContents.chars, .length = config->stringContents.length, .isStaticallyAllocated = false }; char *text = Clay_Cairo__NullTerminate(&toTerminate); char *font_family = fonts[config->fontId]; Clay_BoundingBox bb = command->boundingBox; Clay_Color color = config->textColor; cairo_select_font_face(Clay__Cairo, font_family, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, config->fontSize); cairo_move_to(cr, bb.x, bb.y + bb.height); cairo_set_source_rgba(cr, CLAY_TO_CAIRO(color)); cairo_show_text(cr, text); cairo_close_path(cr); free(text); break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData *config = &command->renderData.border; Clay_BoundingBox bb = command->boundingBox; double top_left_radius = config->cornerRadius.topLeft / 2.0; double top_right_radius = config->cornerRadius.topRight / 2.0; double bottom_right_radius = config->cornerRadius.bottomRight / 2.0; double bottom_left_radius = config->cornerRadius.bottomLeft / 2.0; // Draw the top border if (config->width.top > 0) { cairo_set_line_width(cr, config->width.top); cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color)); cairo_new_sub_path(cr); // Left half-arc for top-left corner cairo_arc(cr, bb.x + top_left_radius, bb.y + top_left_radius, top_left_radius, DEG2RAD(225), DEG2RAD(270)); // Line to right half-arc cairo_line_to(cr, bb.x + bb.width - top_right_radius, bb.y); // Right half-arc for top-right corner cairo_arc(cr, bb.x + bb.width - top_right_radius, bb.y + top_right_radius, top_right_radius, DEG2RAD(270), DEG2RAD(305)); cairo_stroke(cr); } // Draw the right border if (config->width.right > 0) { cairo_set_line_width(cr, config->width.right); cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color)); cairo_new_sub_path(cr); // Top half-arc for top-right corner cairo_arc(cr, bb.x + bb.width - top_right_radius, bb.y + top_right_radius, top_right_radius, DEG2RAD(305), DEG2RAD(350)); // Line to bottom half-arc cairo_line_to(cr, bb.x + bb.width, bb.y + bb.height - bottom_right_radius); // Bottom half-arc for bottom-right corner cairo_arc(cr, bb.x + bb.width - bottom_right_radius, bb.y + bb.height - bottom_right_radius, bottom_right_radius, DEG2RAD(0), DEG2RAD(45)); cairo_stroke(cr); } // Draw the bottom border if (config->width.bottom > 0) { cairo_set_line_width(cr, config->width.bottom); cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color)); cairo_new_sub_path(cr); // Right half-arc for bottom-right corner cairo_arc(cr, bb.x + bb.width - bottom_right_radius, bb.y + bb.height - bottom_right_radius, bottom_right_radius, DEG2RAD(45), DEG2RAD(90)); // Line to left half-arc cairo_line_to(cr, bb.x + bottom_left_radius, bb.y + bb.height); // Left half-arc for bottom-left corner cairo_arc(cr, bb.x + bottom_left_radius, bb.y + bb.height - bottom_left_radius, bottom_left_radius, DEG2RAD(90), DEG2RAD(135)); cairo_stroke(cr); } // Draw the left border if (config->width.left > 0) { cairo_set_line_width(cr, config->width.left); cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color)); cairo_new_sub_path(cr); // Bottom half-arc for bottom-left corner cairo_arc(cr, bb.x + bottom_left_radius, bb.y + bb.height - bottom_left_radius, bottom_left_radius, DEG2RAD(135), DEG2RAD(180)); // Line to top half-arc cairo_line_to(cr, bb.x, bb.y + top_left_radius); // Top half-arc for top-left corner cairo_arc(cr, bb.x + top_left_radius, bb.y + top_left_radius, top_left_radius, DEG2RAD(180), DEG2RAD(225)); cairo_stroke(cr); } break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { Clay_ImageRenderData *config = &command->renderData.image; Clay_BoundingBox bb = command->boundingBox; char *path = config->imageData; cairo_surface_t *surf = cairo_image_surface_create_from_png(path), *origin = cairo_get_target(cr); // Calculate the original image dimensions double image_w = cairo_image_surface_get_width(surf), image_h = cairo_image_surface_get_height(surf); // Calculate the scaling factor to fit within the bounding box while preserving aspect ratio double scale_w = bb.width / image_w; double scale_h = bb.height / image_h; double scale = (scale_w < scale_h) ? scale_w : scale_h; // Use the smaller scaling factor // Apply the same scale to both dimensions to preserve aspect ratio double scale_x = scale; double scale_y = scale; // Calculate the scaled image dimensions double scaled_w = image_w * scale_x; double scaled_h = image_h * scale_y; // Adjust the x and y coordinates to center the scaled image within the bounding box double centered_x = bb.x + (bb.width - scaled_w) / 2.0; double centered_y = bb.y + (bb.height - scaled_h) / 2.0; // Blit the scaled and centered image Clay_Cairo__Blit_Surface(surf, origin, centered_x, centered_y, scale_x, scale_y); // Clean up the source surface cairo_surface_destroy(surf); break; } case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { // Slot your custom elements in here. } default: { fprintf(stderr, "Unknown command type %d\n", (int) command->commandType); } } } } ================================================ FILE: renderers/playdate/clay_renderer_playdate.c ================================================ #include "pd_api.h" #include "../../clay.h" // Playdate drawText function expects the number of codepoints to draw, not byte length static size_t Clay_Playdate_CountUtf8Codepoints(const char *str, size_t byteLen) { size_t count = 0; size_t i = 0; while (i < byteLen) { uint8_t c = (uint8_t)str[i]; if ((c & 0xC0) != 0x80) { count++; } i++; } return count; } // As the playdate can only display black and white, we need to resolve Clay_color to either black or white // for both color and draw mode. static LCDColor clayColorToLCDColor(Clay_Color color) { if (color.r > 0 || color.g > 0 || color.b > 0) { return kColorWhite; } return kColorBlack; } static LCDBitmapDrawMode clayColorToDrawMode(Clay_Color color) { if (color.r > 0 || color.g > 0 || color.b > 0) { return kDrawModeFillWhite; } return kDrawModeCopy; } static float clampCornerRadius(float yAxisSize, float radius) { if (radius < 1.0f) { return 0.0f; } if (radius > yAxisSize / 2) { return yAxisSize / 2; } // Trying to draw a 2x2 ellipse seems to result in just a dot, so if // there is a corner radius at minimum it must be 2 return CLAY__MAX(2, radius); } static void Clay_Playdate_Render(PlaydateAPI *pd, Clay_RenderCommandArray renderCommands, LCDFont **fonts) { for (uint32_t i = 0; i < renderCommands.length; i++) { Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i); Clay_BoundingBox boundingBox = renderCommand->boundingBox; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; float radiusTl = clampCornerRadius(boundingBox.height, config->cornerRadius.topLeft); float radiusTr = clampCornerRadius(boundingBox.height, config->cornerRadius.topRight); float radiusBl = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomLeft); float radiusBr = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomRight); pd->graphics->fillEllipse( boundingBox.x, boundingBox.y, radiusTl * 2, radiusTl * 2, -90.0f, 0.0f, clayColorToLCDColor(config->backgroundColor) ); pd->graphics->fillEllipse( boundingBox.x + boundingBox.width - radiusTr * 2, boundingBox.y, radiusTr * 2, radiusTr * 2, 0.0f, 90.0f, clayColorToLCDColor(config->backgroundColor) ); pd->graphics->fillEllipse( boundingBox.x + boundingBox.width - radiusBr * 2, boundingBox.y + boundingBox.height - radiusBr * 2, radiusBr * 2, radiusBr * 2, 90.0f, 180.0f, clayColorToLCDColor(config->backgroundColor) ); pd->graphics->fillEllipse( boundingBox.x, boundingBox.y + boundingBox.height - radiusBl * 2, radiusBl * 2, radiusBl * 2, 180.0f, 270.0f, clayColorToLCDColor(config->backgroundColor) ); // Top chunk pd->graphics->fillRect( boundingBox.x + radiusTl, boundingBox.y, boundingBox.width - radiusTl - radiusTr, CLAY__MAX(radiusTl, radiusTr), clayColorToLCDColor(config->backgroundColor) ); // bottom chunk int bottomChunkHeight = CLAY__MAX(radiusBl, radiusBr); pd->graphics->fillRect( boundingBox.x + radiusBl, boundingBox.y + boundingBox.height - bottomChunkHeight, boundingBox.width - radiusBl - radiusBr, bottomChunkHeight, clayColorToLCDColor(config->backgroundColor) ); // Middle chunk int middleChunkHeight = boundingBox.height - CLAY__MIN(radiusBr, radiusBl) - CLAY__MIN(radiusTr, radiusTl); pd->graphics->fillRect( boundingBox.x + CLAY__MIN(radiusTl, radiusBl), boundingBox.y + CLAY__MIN(radiusTr, radiusTl), boundingBox.width - radiusBl - radiusBr, middleChunkHeight, clayColorToLCDColor(config->backgroundColor) ); // Left chunk int leftChunkHeight = boundingBox.height - radiusTl - radiusBl; int leftChunkWidth = CLAY__MAX(radiusTl, radiusBl); pd->graphics->fillRect( boundingBox.x, boundingBox.y + radiusTl, leftChunkWidth, leftChunkHeight, clayColorToLCDColor(config->backgroundColor) ); // Right chunk int rightChunkHeight = boundingBox.height - radiusTr - radiusBr; int rightChunkWidth = CLAY__MAX(radiusTr, radiusBr); pd->graphics->fillRect( boundingBox.x + boundingBox.width - rightChunkWidth, boundingBox.y + radiusTr, rightChunkWidth, rightChunkHeight, clayColorToLCDColor(config->backgroundColor) ); break; } case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData *config = &renderCommand->renderData.text; LCDFont *font = fonts[config->fontId]; pd->graphics->setFont(font); pd->graphics->setDrawMode(clayColorToDrawMode(config->textColor)); pd->graphics->drawText( renderCommand->renderData.text.stringContents.chars, Clay_Playdate_CountUtf8Codepoints( renderCommand->renderData.text.stringContents.chars, renderCommand->renderData.text.stringContents.length ), kUTF8Encoding, boundingBox.x, boundingBox.y ); pd->graphics->setDrawMode(kDrawModeCopy); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { pd->graphics->setClipRect( boundingBox.x,boundingBox.y, boundingBox.width, boundingBox.height ); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { pd->graphics->clearClipRect(); break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { Clay_ImageRenderData *config = &renderCommand->renderData.image; LCDBitmap *texture = config->imageData; int texWidth; int texHeight; pd->graphics->getBitmapData(texture, &texWidth, &texHeight, NULL, NULL, NULL); if (texWidth != boundingBox.width || texHeight != boundingBox.height) { pd->graphics->drawScaledBitmap( texture, boundingBox.x, boundingBox.y, boundingBox.width / texWidth, boundingBox.height / texHeight ); } else { pd->graphics->drawBitmap(texture, boundingBox.x, boundingBox.y, kBitmapUnflipped); } break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData *config = &renderCommand->renderData.border; float radiusTl = clampCornerRadius(boundingBox.height, config->cornerRadius.topLeft); float radiusTr = clampCornerRadius(boundingBox.height, config->cornerRadius.topRight); float radiusBl = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomLeft); float radiusBr = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomRight); if (config->width.top > 0) { pd->graphics->drawEllipse( boundingBox.x, boundingBox.y, radiusTl * 2, radiusTl * 2, config->width.top, -90.0f, 0.0f, clayColorToLCDColor(config->color) ); pd->graphics->drawLine( boundingBox.x + radiusTl, boundingBox.y, boundingBox.x + boundingBox.width - radiusTr - config->width.right, boundingBox.y, config->width.top, clayColorToLCDColor(config->color) ); pd->graphics->drawEllipse( boundingBox.x + boundingBox.width - radiusTr * 2, boundingBox.y, radiusTr * 2, radiusTr * 2, config->width.top, 0.0f, 90.0f, clayColorToLCDColor(config->color) ); } if (config->width.right > 0 && radiusTr + radiusBr <= boundingBox.height) { pd->graphics->drawLine( boundingBox.x + boundingBox.width - config->width.right, boundingBox.y + radiusTr, boundingBox.x + boundingBox.width - config->width.right, boundingBox.y + boundingBox.height - radiusBr - config->width.bottom, config->width.right, clayColorToLCDColor(config->color) ); } if (config->width.bottom > 0) { pd->graphics->drawEllipse( boundingBox.x + boundingBox.width - radiusBr * 2, boundingBox.y + boundingBox.height - radiusBr * 2, radiusBr * 2, radiusBr * 2, config->width.bottom, 90.0f, 180.0f, clayColorToLCDColor(config->color) ); pd->graphics->drawLine( boundingBox.x + boundingBox.width - radiusBr - config->width.right, boundingBox.y + boundingBox.height - config->width.bottom, boundingBox.x + radiusBl, boundingBox.y + boundingBox.height - config->width.bottom, config->width.bottom, clayColorToLCDColor(config->color) ); pd->graphics->drawEllipse( boundingBox.x, boundingBox.y + boundingBox.height - radiusBl * 2, radiusBl * 2, radiusBl * 2, config->width.bottom, 180.0f, 270.0f, clayColorToLCDColor(config->color) ); } if (config->width.left > 0 && radiusBl + radiusTl < boundingBox.height) { pd->graphics->drawLine( boundingBox.x, boundingBox.y + boundingBox.height - radiusBl - config->width.bottom, boundingBox.x, boundingBox.y + radiusTl, config->width.left, clayColorToLCDColor(config->color) ); } break; } default: { pd->system->logToConsole("Error: unhandled render command: %d\n", renderCommand->commandType); return; } } } } ================================================ FILE: renderers/raylib/clay_renderer_raylib.c ================================================ #include "raylib.h" #include "raymath.h" #include "stdint.h" #include "string.h" #include "stdio.h" #include "stdlib.h" #define CLAY_RECTANGLE_TO_RAYLIB_RECTANGLE(rectangle) (Rectangle) { .x = rectangle.x, .y = rectangle.y, .width = rectangle.width, .height = rectangle.height } #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) } Camera Raylib_camera; typedef enum { CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL } CustomLayoutElementType; typedef struct { Model model; float scale; Vector3 position; Matrix rotation; } CustomLayoutElement_3DModel; typedef struct { CustomLayoutElementType type; union { CustomLayoutElement_3DModel model; } customData; } CustomLayoutElement; // Get a ray trace from the screen position (i.e mouse) within a specific section of the screen Ray GetScreenToWorldPointWithZDistance(Vector2 position, Camera camera, int screenWidth, int screenHeight, float zDistance) { Ray ray = { 0 }; // Calculate normalized device coordinates // NOTE: y value is negative float x = (2.0f*position.x)/(float)screenWidth - 1.0f; float y = 1.0f - (2.0f*position.y)/(float)screenHeight; float z = 1.0f; // Store values in a vector Vector3 deviceCoords = { x, y, z }; // Calculate view matrix from camera look at Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); Matrix matProj = MatrixIdentity(); if (camera.projection == CAMERA_PERSPECTIVE) { // Calculate projection matrix from perspective matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)screenWidth/(double)screenHeight), 0.01f, zDistance); } else if (camera.projection == CAMERA_ORTHOGRAPHIC) { double aspect = (double)screenWidth/(double)screenHeight; double top = camera.fovy/2.0; double right = top*aspect; // Calculate projection matrix from orthographic matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); } // Unproject far/near points Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); // Calculate normalized direction vector Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); ray.position = farPoint; // Apply calculated vectors to ray ray.direction = direction; return ray; } static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { // Measure string size for Font Clay_Dimensions textSize = { 0 }; float maxTextWidth = 0.0f; float lineTextWidth = 0; int maxLineCharCount = 0; int lineCharCount = 0; float textHeight = config->fontSize; Font* fonts = (Font*)userData; Font fontToUse = fonts[config->fontId]; // Font failed to load, likely the fonts are in the wrong place relative to the execution dir. // RayLib ships with a default font, so we can continue with that built in one. if (!fontToUse.glyphs) { fontToUse = GetFontDefault(); } float scaleFactor = config->fontSize/(float)fontToUse.baseSize; for (int i = 0; i < text.length; ++i, lineCharCount++) { if (text.chars[i] == '\n') { maxTextWidth = fmax(maxTextWidth, lineTextWidth); maxLineCharCount = CLAY__MAX(maxLineCharCount, lineCharCount); lineTextWidth = 0; lineCharCount = 0; continue; } int index = text.chars[i] - 32; if (fontToUse.glyphs[index].advanceX != 0) lineTextWidth += fontToUse.glyphs[index].advanceX; else lineTextWidth += (fontToUse.recs[index].width + fontToUse.glyphs[index].offsetX); } maxTextWidth = fmax(maxTextWidth, lineTextWidth); maxLineCharCount = CLAY__MAX(maxLineCharCount, lineCharCount); textSize.width = maxTextWidth * scaleFactor + (lineCharCount * config->letterSpacing); textSize.height = textHeight; return textSize; } void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned int flags) { SetConfigFlags(flags); InitWindow(width, height, title); // EnableEventWaiting(); } // A MALLOC'd buffer, that we keep modifying inorder to save from so many Malloc and Free Calls. // Call Clay_Raylib_Close() to free static char *temp_render_buffer = NULL; static int temp_render_buffer_len = 0; // Call after closing the window to clean up the render buffer void Clay_Raylib_Close() { if(temp_render_buffer) free(temp_render_buffer); temp_render_buffer_len = 0; CloseWindow(); } void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts) { for (int j = 0; j < renderCommands.length; j++) { Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); Clay_BoundingBox boundingBox = {roundf(renderCommand->boundingBox.x), roundf(renderCommand->boundingBox.y), roundf(renderCommand->boundingBox.width), roundf(renderCommand->boundingBox.height)}; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData *textData = &renderCommand->renderData.text; Font fontToUse = fonts[textData->fontId]; int strlen = textData->stringContents.length + 1; if(strlen > temp_render_buffer_len) { // Grow the temp buffer if we need a larger string if(temp_render_buffer) free(temp_render_buffer); temp_render_buffer = (char *) malloc(strlen); temp_render_buffer_len = strlen; } // Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator memcpy(temp_render_buffer, textData->stringContents.chars, textData->stringContents.length); temp_render_buffer[textData->stringContents.length] = '\0'; DrawTextEx(fontToUse, temp_render_buffer, (Vector2){boundingBox.x, boundingBox.y}, (float)textData->fontSize, (float)textData->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(textData->textColor)); break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { Texture2D imageTexture = *(Texture2D *)renderCommand->renderData.image.imageData; Clay_Color tintColor = renderCommand->renderData.image.backgroundColor; if (tintColor.r == 0 && tintColor.g == 0 && tintColor.b == 0 && tintColor.a == 0) { tintColor = (Clay_Color) { 255, 255, 255, 255 }; } DrawTexturePro( imageTexture, (Rectangle) { 0, 0, imageTexture.width, imageTexture.height }, (Rectangle){boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height}, (Vector2) {}, 0, CLAY_COLOR_TO_RAYLIB_COLOR(tintColor)); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { BeginScissorMode((int)roundf(boundingBox.x), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width), (int)roundf(boundingBox.height)); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { EndScissorMode(); break; } case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; if (config->cornerRadius.topLeft > 0) { float radius = (config->cornerRadius.topLeft * 2) / (float)((boundingBox.width > boundingBox.height) ? boundingBox.height : boundingBox.width); DrawRectangleRounded((Rectangle) { boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height }, radius, 8, CLAY_COLOR_TO_RAYLIB_COLOR(config->backgroundColor)); } else { DrawRectangle(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height, CLAY_COLOR_TO_RAYLIB_COLOR(config->backgroundColor)); } break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData *config = &renderCommand->renderData.border; // Left border if (config->width.left > 0) { 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)); } // Right border if (config->width.right > 0) { 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)); } // Top border if (config->width.top > 0) { 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)); } // Bottom border if (config->width.bottom > 0) { 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)); } if (config->cornerRadius.topLeft > 0) { 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)); } if (config->cornerRadius.topRight > 0) { DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.topRight), roundf(boundingBox.y + config->cornerRadius.topRight) }, roundf(config->cornerRadius.topRight - config->width.top), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); } if (config->cornerRadius.bottomLeft > 0) { DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->width.bottom), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); } if (config->cornerRadius.bottomRight > 0) { DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.bottomRight), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomRight) }, roundf(config->cornerRadius.bottomRight - config->width.bottom), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); } break; } case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { Clay_CustomRenderData *config = &renderCommand->renderData.custom; CustomLayoutElement *customElement = (CustomLayoutElement *)config->customData; if (!customElement) continue; switch (customElement->type) { case CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL: { Clay_BoundingBox rootBox = renderCommands.internalArray[0].boundingBox; float scaleValue = CLAY__MIN(CLAY__MIN(1, 768 / rootBox.height) * CLAY__MAX(1, rootBox.width / 1024), 1.5f); 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); BeginMode3D(Raylib_camera); DrawModel(customElement->customData.model.model, positionRay.position, customElement->customData.model.scale * scaleValue, WHITE); // Draw 3d model with texture EndMode3D(); break; } default: break; } break; } default: { printf("Error: unhandled render command."); exit(1); } } } } ================================================ FILE: renderers/raylib/raylib.h ================================================ /********************************************************************************************** * * raylib v5.5 - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) * * FEATURES: * - NO external dependencies, all required libraries included with raylib * - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, * MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5. * - Written in plain C code (C99) in PascalCase/camelCase notation * - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) * - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] * - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) * - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) * - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! * - Flexible Materials system, supporting classic maps and PBR maps * - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) * - Shaders support, including Model shaders and Postprocessing shaders * - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] * - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) * - VR stereo rendering with configurable HMD device parameters * - Bindings to multiple programming languages available! * * NOTES: * - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] * - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) * - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) * - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) * * DEPENDENCIES (included): * [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input * [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input * [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading * [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management * * OPTIONAL DEPENDENCIES (included): * [rcore] msf_gif (Miles Fogle) for GIF recording * [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm * [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm * [rcore] rprand (Ramon Snatamaria) for pseudo-random numbers generation * [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage * [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) * [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) * [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms * [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation * [rtext] stb_truetype (Sean Barret) for ttf fonts loading * [rtext] stb_rect_pack (Sean Barret) for rectangles packing * [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation * [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) * [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) * [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) * [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) * [raudio] dr_wav (David Reid) for WAV audio file loading * [raudio] dr_flac (David Reid) for FLAC audio file loading * [raudio] dr_mp3 (David Reid) for MP3 audio file loading * [raudio] stb_vorbis (Sean Barret) for OGG audio loading * [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading * [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading * [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage * * * LICENSE: zlib/libpng * * raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, * BSD-like license that allows static linking with closed source software: * * Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, including commercial * applications, and to alter it and redistribute it freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not claim that you * wrote the original software. If you use this software in a product, an acknowledgment * in the product documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be misrepresented * as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * **********************************************************************************************/ #ifndef RAYLIB_H #define RAYLIB_H #include // Required for: va_list - Only used by TraceLogCallback #define RAYLIB_VERSION_MAJOR 5 #define RAYLIB_VERSION_MINOR 5 #define RAYLIB_VERSION_PATCH 0 #define RAYLIB_VERSION "5.5" // Function specifiers in case library is build/used as a shared library // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll // NOTE: visibility("default") attribute makes symbols "visible" when compiled with -fvisibility=hidden #if defined(_WIN32) #if defined(__TINYC__) #define __declspec(x) __attribute__((x)) #endif #if defined(BUILD_LIBTYPE_SHARED) #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) #elif defined(USE_LIBTYPE_SHARED) #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) #endif #else #if defined(BUILD_LIBTYPE_SHARED) #define RLAPI __attribute__((visibility("default"))) // We are building as a Unix shared library (.so/.dylib) #endif #endif #ifndef RLAPI #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) #endif //---------------------------------------------------------------------------------- // Some basic Defines //---------------------------------------------------------------------------------- #ifndef PI #define PI 3.14159265358979323846f #endif #ifndef DEG2RAD #define DEG2RAD (PI/180.0f) #endif #ifndef RAD2DEG #define RAD2DEG (180.0f/PI) #endif // Allow custom memory allocators // NOTE: Require recompiling raylib sources #ifndef RL_MALLOC #define RL_MALLOC(sz) malloc(sz) #endif #ifndef RL_CALLOC #define RL_CALLOC(n,sz) calloc(n,sz) #endif #ifndef RL_REALLOC #define RL_REALLOC(ptr,sz) realloc(ptr,sz) #endif #ifndef RL_FREE #define RL_FREE(ptr) free(ptr) #endif // NOTE: MSVC C++ compiler does not support compound literals (C99 feature) // Plain structures in C++ (without constructors) can be initialized with { } // This is called aggregate initialization (C++11 feature) #if defined(__cplusplus) #define CLITERAL(type) type #else #define CLITERAL(type) (type) #endif // Some compilers (mostly macos clang) default to C++98, // where aggregate initialization can't be used // So, give a more clear error stating how to fix this #if !defined(_MSC_VER) && (defined(__cplusplus) && __cplusplus < 201103L) #error "C++11 or later is required. Add -std=c++11" #endif // NOTE: We set some defines with some data types declared by raylib // Other modules (raymath, rlgl) also require some of those types, so, // to be able to use those other modules as standalone (not depending on raylib) // this defines are very useful for internal check and avoid type (re)definitions #define RL_COLOR_TYPE #define RL_RECTANGLE_TYPE #define RL_VECTOR2_TYPE #define RL_VECTOR3_TYPE #define RL_VECTOR4_TYPE #define RL_QUATERNION_TYPE #define RL_MATRIX_TYPE // Some Basic Colors // NOTE: Custom raylib color palette for amazing visuals on WHITE background #define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray #define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray #define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray #define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow #define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold #define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange #define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink #define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red #define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon #define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green #define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime #define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green #define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue #define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue #define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue #define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple #define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet #define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple #define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige #define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown #define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown #define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White #define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black #define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) #define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta #define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) //---------------------------------------------------------------------------------- // Structures Definition //---------------------------------------------------------------------------------- // Boolean type #if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) #include #elif !defined(__cplusplus) && !defined(bool) typedef enum bool { false = 0, true = !false } bool; #define RL_BOOL_TYPE #endif // Vector2, 2 components typedef struct Vector2 { float x; // Vector x component float y; // Vector y component } Vector2; // Vector3, 3 components typedef struct Vector3 { float x; // Vector x component float y; // Vector y component float z; // Vector z component } Vector3; // Vector4, 4 components typedef struct Vector4 { float x; // Vector x component float y; // Vector y component float z; // Vector z component float w; // Vector w component } Vector4; // Quaternion, 4 components (Vector4 alias) typedef Vector4 Quaternion; // Matrix, 4x4 components, column major, OpenGL style, right-handed typedef struct Matrix { float m0, m4, m8, m12; // Matrix first row (4 components) float m1, m5, m9, m13; // Matrix second row (4 components) float m2, m6, m10, m14; // Matrix third row (4 components) float m3, m7, m11, m15; // Matrix fourth row (4 components) } Matrix; // Color, 4 components, R8G8B8A8 (32bit) typedef struct Color { unsigned char r; // Color red value unsigned char g; // Color green value unsigned char b; // Color blue value unsigned char a; // Color alpha value } Color; // Rectangle, 4 components typedef struct Rectangle { float x; // Rectangle top-left corner position x float y; // Rectangle top-left corner position y float width; // Rectangle width float height; // Rectangle height } Rectangle; // Image, pixel data stored in CPU memory (RAM) typedef struct Image { void *data; // Image raw data int width; // Image base width int height; // Image base height int mipmaps; // Mipmap levels, 1 by default int format; // Data format (PixelFormat type) } Image; // Texture, tex data stored in GPU memory (VRAM) typedef struct Texture { unsigned int id; // OpenGL texture id int width; // Texture base width int height; // Texture base height int mipmaps; // Mipmap levels, 1 by default int format; // Data format (PixelFormat type) } Texture; // Texture2D, same as Texture typedef Texture Texture2D; // TextureCubemap, same as Texture typedef Texture TextureCubemap; // RenderTexture, fbo for texture rendering typedef struct RenderTexture { unsigned int id; // OpenGL framebuffer object id Texture texture; // Color buffer attachment texture Texture depth; // Depth buffer attachment texture } RenderTexture; // RenderTexture2D, same as RenderTexture typedef RenderTexture RenderTexture2D; // NPatchInfo, n-patch layout info typedef struct NPatchInfo { Rectangle source; // Texture source rectangle int left; // Left border offset int top; // Top border offset int right; // Right border offset int bottom; // Bottom border offset int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 } NPatchInfo; // GlyphInfo, font characters glyphs info typedef struct GlyphInfo { int value; // Character value (Unicode) int offsetX; // Character offset X when drawing int offsetY; // Character offset Y when drawing int advanceX; // Character advance position X Image image; // Character image data } GlyphInfo; // Font, font texture and GlyphInfo array data typedef struct Font { int baseSize; // Base size (default chars height) int glyphCount; // Number of glyph characters int glyphPadding; // Padding around the glyph characters Texture2D texture; // Texture atlas containing the glyphs Rectangle *recs; // Rectangles in texture for the glyphs GlyphInfo *glyphs; // Glyphs info data } Font; // Camera, defines position/orientation in 3d space typedef struct Camera3D { Vector3 position; // Camera position Vector3 target; // Camera target it looks-at Vector3 up; // Camera up vector (rotation over its axis) float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane width in orthographic int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC } Camera3D; typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D // Camera2D, defines position/orientation in 2d space typedef struct Camera2D { Vector2 offset; // Camera offset (displacement from target) Vector2 target; // Camera target (rotation and zoom origin) float rotation; // Camera rotation in degrees float zoom; // Camera zoom (scaling), should be 1.0f by default } Camera2D; // Mesh, vertex data and vao/vbo typedef struct Mesh { int vertexCount; // Number of vertices stored in arrays int triangleCount; // Number of triangles stored (indexed or not) // Vertex attributes data float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) float *texcoords2; // Vertex texture second coordinates (UV - 2 components per vertex) (shader-location = 5) float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) unsigned short *indices; // Vertex indices (in case vertex data comes indexed) // Animation vertex data float *animVertices; // Animated vertex positions (after bones transformations) float *animNormals; // Animated normals (after bones transformations) unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) Matrix *boneMatrices; // Bones animated transformation matrices int boneCount; // Number of bones // OpenGL identifiers unsigned int vaoId; // OpenGL Vertex Array Object id unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) } Mesh; // Shader typedef struct Shader { unsigned int id; // Shader program id int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) } Shader; // MaterialMap typedef struct MaterialMap { Texture2D texture; // Material map texture Color color; // Material map color float value; // Material map value } MaterialMap; // Material, includes shader and maps typedef struct Material { Shader shader; // Material shader MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) float params[4]; // Material generic parameters (if required) } Material; // Transform, vertex transformation data typedef struct Transform { Vector3 translation; // Translation Quaternion rotation; // Rotation Vector3 scale; // Scale } Transform; // Bone, skeletal animation bone typedef struct BoneInfo { char name[32]; // Bone name int parent; // Bone parent } BoneInfo; // Model, meshes, materials and animation data typedef struct Model { Matrix transform; // Local transform matrix int meshCount; // Number of meshes int materialCount; // Number of materials Mesh *meshes; // Meshes array Material *materials; // Materials array int *meshMaterial; // Mesh material number // Animation data int boneCount; // Number of bones BoneInfo *bones; // Bones information (skeleton) Transform *bindPose; // Bones base transformation (pose) } Model; // ModelAnimation typedef struct ModelAnimation { int boneCount; // Number of bones int frameCount; // Number of animation frames BoneInfo *bones; // Bones information (skeleton) Transform **framePoses; // Poses array by frame char name[32]; // Animation name } ModelAnimation; // Ray, ray for raycasting typedef struct Ray { Vector3 position; // Ray position (origin) Vector3 direction; // Ray direction (normalized) } Ray; // RayCollision, ray hit information typedef struct RayCollision { bool hit; // Did the ray hit something? float distance; // Distance to the nearest hit Vector3 point; // Point of the nearest hit Vector3 normal; // Surface normal of hit } RayCollision; // BoundingBox typedef struct BoundingBox { Vector3 min; // Minimum vertex box-corner Vector3 max; // Maximum vertex box-corner } BoundingBox; // Wave, audio wave data typedef struct Wave { unsigned int frameCount; // Total number of frames (considering channels) unsigned int sampleRate; // Frequency (samples per second) unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) void *data; // Buffer data pointer } Wave; // Opaque structs declaration // NOTE: Actual structs are defined internally in raudio module typedef struct rAudioBuffer rAudioBuffer; typedef struct rAudioProcessor rAudioProcessor; // AudioStream, custom audio stream typedef struct AudioStream { rAudioBuffer *buffer; // Pointer to internal data used by the audio system rAudioProcessor *processor; // Pointer to internal data processor, useful for audio effects unsigned int sampleRate; // Frequency (samples per second) unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) } AudioStream; // Sound typedef struct Sound { AudioStream stream; // Audio stream unsigned int frameCount; // Total number of frames (considering channels) } Sound; // Music, audio stream, anything longer than ~10 seconds should be streamed typedef struct Music { AudioStream stream; // Audio stream unsigned int frameCount; // Total number of frames (considering channels) bool looping; // Music looping enable int ctxType; // Type of music context (audio filetype) void *ctxData; // Audio context data, depends on type } Music; // VrDeviceInfo, Head-Mounted-Display device parameters typedef struct VrDeviceInfo { int hResolution; // Horizontal resolution in pixels int vResolution; // Vertical resolution in pixels float hScreenSize; // Horizontal size in meters float vScreenSize; // Vertical size in meters float eyeToScreenDistance; // Distance between eye and display in meters float lensSeparationDistance; // Lens separation distance in meters float interpupillaryDistance; // IPD (distance between pupils) in meters float lensDistortionValues[4]; // Lens distortion constant parameters float chromaAbCorrection[4]; // Chromatic aberration correction parameters } VrDeviceInfo; // VrStereoConfig, VR stereo rendering configuration for simulator typedef struct VrStereoConfig { Matrix projection[2]; // VR projection matrices (per eye) Matrix viewOffset[2]; // VR view offset matrices (per eye) float leftLensCenter[2]; // VR left lens center float rightLensCenter[2]; // VR right lens center float leftScreenCenter[2]; // VR left screen center float rightScreenCenter[2]; // VR right screen center float scale[2]; // VR distortion scale float scaleIn[2]; // VR distortion scale in } VrStereoConfig; // File path list typedef struct FilePathList { unsigned int capacity; // Filepaths max entries unsigned int count; // Filepaths entries count char **paths; // Filepaths entries } FilePathList; // Automation event typedef struct AutomationEvent { unsigned int frame; // Event frame unsigned int type; // Event type (AutomationEventType) int params[4]; // Event parameters (if required) } AutomationEvent; // Automation event list typedef struct AutomationEventList { unsigned int capacity; // Events max entries (MAX_AUTOMATION_EVENTS) unsigned int count; // Events entries count AutomationEvent *events; // Events entries } AutomationEventList; //---------------------------------------------------------------------------------- // Enumerators Definition //---------------------------------------------------------------------------------- // System/Window config flags // NOTE: Every bit registers one state (use it with bit masks) // By default all flags are set to 0 typedef enum { FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI FLAG_WINDOW_MOUSE_PASSTHROUGH = 0x00004000, // Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED FLAG_BORDERLESS_WINDOWED_MODE = 0x00008000, // Set to run program in borderless windowed mode FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) } ConfigFlags; // Trace log level // NOTE: Organized by priority level typedef enum { LOG_ALL = 0, // Display all logs LOG_TRACE, // Trace logging, intended for internal use only LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds LOG_INFO, // Info logging, used for program execution info LOG_WARNING, // Warning logging, used on recoverable failures LOG_ERROR, // Error logging, used on unrecoverable failures LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) LOG_NONE // Disable logging } TraceLogLevel; // Keyboard keys (US keyboard layout) // NOTE: Use GetKeyPressed() to allow redefining // required keys for alternative layouts typedef enum { KEY_NULL = 0, // Key: NULL, used for no key pressed // Alphanumeric keys KEY_APOSTROPHE = 39, // Key: ' KEY_COMMA = 44, // Key: , KEY_MINUS = 45, // Key: - KEY_PERIOD = 46, // Key: . KEY_SLASH = 47, // Key: / KEY_ZERO = 48, // Key: 0 KEY_ONE = 49, // Key: 1 KEY_TWO = 50, // Key: 2 KEY_THREE = 51, // Key: 3 KEY_FOUR = 52, // Key: 4 KEY_FIVE = 53, // Key: 5 KEY_SIX = 54, // Key: 6 KEY_SEVEN = 55, // Key: 7 KEY_EIGHT = 56, // Key: 8 KEY_NINE = 57, // Key: 9 KEY_SEMICOLON = 59, // Key: ; KEY_EQUAL = 61, // Key: = KEY_A = 65, // Key: A | a KEY_B = 66, // Key: B | b KEY_C = 67, // Key: C | c KEY_D = 68, // Key: D | d KEY_E = 69, // Key: E | e KEY_F = 70, // Key: F | f KEY_G = 71, // Key: G | g KEY_H = 72, // Key: H | h KEY_I = 73, // Key: I | i KEY_J = 74, // Key: J | j KEY_K = 75, // Key: K | k KEY_L = 76, // Key: L | l KEY_M = 77, // Key: M | m KEY_N = 78, // Key: N | n KEY_O = 79, // Key: O | o KEY_P = 80, // Key: P | p KEY_Q = 81, // Key: Q | q KEY_R = 82, // Key: R | r KEY_S = 83, // Key: S | s KEY_T = 84, // Key: T | t KEY_U = 85, // Key: U | u KEY_V = 86, // Key: V | v KEY_W = 87, // Key: W | w KEY_X = 88, // Key: X | x KEY_Y = 89, // Key: Y | y KEY_Z = 90, // Key: Z | z KEY_LEFT_BRACKET = 91, // Key: [ KEY_BACKSLASH = 92, // Key: '\' KEY_RIGHT_BRACKET = 93, // Key: ] KEY_GRAVE = 96, // Key: ` // Function keys KEY_SPACE = 32, // Key: Space KEY_ESCAPE = 256, // Key: Esc KEY_ENTER = 257, // Key: Enter KEY_TAB = 258, // Key: Tab KEY_BACKSPACE = 259, // Key: Backspace KEY_INSERT = 260, // Key: Ins KEY_DELETE = 261, // Key: Del KEY_RIGHT = 262, // Key: Cursor right KEY_LEFT = 263, // Key: Cursor left KEY_DOWN = 264, // Key: Cursor down KEY_UP = 265, // Key: Cursor up KEY_PAGE_UP = 266, // Key: Page up KEY_PAGE_DOWN = 267, // Key: Page down KEY_HOME = 268, // Key: Home KEY_END = 269, // Key: End KEY_CAPS_LOCK = 280, // Key: Caps lock KEY_SCROLL_LOCK = 281, // Key: Scroll down KEY_NUM_LOCK = 282, // Key: Num lock KEY_PRINT_SCREEN = 283, // Key: Print screen KEY_PAUSE = 284, // Key: Pause KEY_F1 = 290, // Key: F1 KEY_F2 = 291, // Key: F2 KEY_F3 = 292, // Key: F3 KEY_F4 = 293, // Key: F4 KEY_F5 = 294, // Key: F5 KEY_F6 = 295, // Key: F6 KEY_F7 = 296, // Key: F7 KEY_F8 = 297, // Key: F8 KEY_F9 = 298, // Key: F9 KEY_F10 = 299, // Key: F10 KEY_F11 = 300, // Key: F11 KEY_F12 = 301, // Key: F12 KEY_LEFT_SHIFT = 340, // Key: Shift left KEY_LEFT_CONTROL = 341, // Key: Control left KEY_LEFT_ALT = 342, // Key: Alt left KEY_LEFT_SUPER = 343, // Key: Super left KEY_RIGHT_SHIFT = 344, // Key: Shift right KEY_RIGHT_CONTROL = 345, // Key: Control right KEY_RIGHT_ALT = 346, // Key: Alt right KEY_RIGHT_SUPER = 347, // Key: Super right KEY_KB_MENU = 348, // Key: KB menu // Keypad keys KEY_KP_0 = 320, // Key: Keypad 0 KEY_KP_1 = 321, // Key: Keypad 1 KEY_KP_2 = 322, // Key: Keypad 2 KEY_KP_3 = 323, // Key: Keypad 3 KEY_KP_4 = 324, // Key: Keypad 4 KEY_KP_5 = 325, // Key: Keypad 5 KEY_KP_6 = 326, // Key: Keypad 6 KEY_KP_7 = 327, // Key: Keypad 7 KEY_KP_8 = 328, // Key: Keypad 8 KEY_KP_9 = 329, // Key: Keypad 9 KEY_KP_DECIMAL = 330, // Key: Keypad . KEY_KP_DIVIDE = 331, // Key: Keypad / KEY_KP_MULTIPLY = 332, // Key: Keypad * KEY_KP_SUBTRACT = 333, // Key: Keypad - KEY_KP_ADD = 334, // Key: Keypad + KEY_KP_ENTER = 335, // Key: Keypad Enter KEY_KP_EQUAL = 336, // Key: Keypad = // Android key buttons KEY_BACK = 4, // Key: Android back button KEY_MENU = 5, // Key: Android menu button KEY_VOLUME_UP = 24, // Key: Android volume up button KEY_VOLUME_DOWN = 25 // Key: Android volume down button } KeyboardKey; // Add backwards compatibility support for deprecated names #define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT #define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT #define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE // Mouse buttons typedef enum { MOUSE_BUTTON_LEFT = 0, // Mouse button left MOUSE_BUTTON_RIGHT = 1, // Mouse button right MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) MOUSE_BUTTON_FORWARD = 5, // Mouse button forward (advanced mouse device) MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) } MouseButton; // Mouse cursor typedef enum { MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape MOUSE_CURSOR_ARROW = 1, // Arrow shape MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape MOUSE_CURSOR_RESIZE_ALL = 9, // The omnidirectional resize/move cursor shape MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape } MouseCursor; // Gamepad buttons typedef enum { GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, just for error checking GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Circle, Xbox: B) GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Square, Xbox: X) GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (first), it could be a trailing button GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right } GamepadButton; // Gamepad axis typedef enum { GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] } GamepadAxis; // Material map index typedef enum { MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) MATERIAL_MAP_NORMAL, // Normal material MATERIAL_MAP_ROUGHNESS, // Roughness material MATERIAL_MAP_OCCLUSION, // Ambient occlusion material MATERIAL_MAP_EMISSION, // Emission material MATERIAL_MAP_HEIGHT, // Heightmap material MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) MATERIAL_MAP_BRDF // Brdf material } MaterialMapIndex; #define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO #define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS // Shader location index typedef enum { SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices } ShaderLocationIndex; #define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO #define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS // Shader uniform data type typedef enum { SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) SHADER_UNIFORM_INT, // Shader uniform type: int SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d } ShaderUniformDataType; // Shader attribute data types typedef enum { SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) } ShaderAttributeDataType; // Pixel formats // NOTE: Support depends on OpenGL version and platform typedef enum { PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp } PixelFormat; // Texture parameters: filter mode // NOTE 1: Filtering considers mipmaps if available in the texture // NOTE 2: Filter is accordingly set for minification and magnification typedef enum { TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation TEXTURE_FILTER_BILINEAR, // Linear filtering TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x } TextureFilter; // Texture parameters: wrap mode typedef enum { TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode } TextureWrap; // Cubemap layouts typedef enum { CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces } CubemapLayout; // Font type, defines generation method typedef enum { FONT_DEFAULT = 0, // Default font generation, anti-aliased FONT_BITMAP, // Bitmap font generation, no anti-aliasing FONT_SDF // SDF font generation, requires external shader } FontType; // Color blending modes (pre-defined) typedef enum { BLEND_ALPHA = 0, // Blend textures considering alpha (default) BLEND_ADDITIVE, // Blend textures adding colors BLEND_MULTIPLIED, // Blend textures multiplying colors BLEND_ADD_COLORS, // Blend textures adding colors (alternative) BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) } BlendMode; // Gesture // NOTE: Provided as bit-wise flags to enable only desired gestures typedef enum { GESTURE_NONE = 0, // No gesture GESTURE_TAP = 1, // Tap gesture GESTURE_DOUBLETAP = 2, // Double tap gesture GESTURE_HOLD = 4, // Hold gesture GESTURE_DRAG = 8, // Drag gesture GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture GESTURE_SWIPE_LEFT = 32, // Swipe left gesture GESTURE_SWIPE_UP = 64, // Swipe up gesture GESTURE_SWIPE_DOWN = 128, // Swipe down gesture GESTURE_PINCH_IN = 256, // Pinch in gesture GESTURE_PINCH_OUT = 512 // Pinch out gesture } Gesture; // Camera system modes typedef enum { CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) CAMERA_FREE, // Camera free mode CAMERA_ORBITAL, // Camera orbital, around target, zoom supported CAMERA_FIRST_PERSON, // Camera first person CAMERA_THIRD_PERSON // Camera third person } CameraMode; // Camera projection typedef enum { CAMERA_PERSPECTIVE = 0, // Perspective projection CAMERA_ORTHOGRAPHIC // Orthographic projection } CameraProjection; // N-patch layout typedef enum { NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles } NPatchLayout; // Callbacks to hook some internal functions // WARNING: These callbacks are intended for advanced users typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, int dataSize); // FileIO: Save binary data typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data //------------------------------------------------------------------------------------ // Global Variables Definition //------------------------------------------------------------------------------------ // It's lonely here... //------------------------------------------------------------------------------------ // Window and Graphics Device Functions (Module: core) //------------------------------------------------------------------------------------ #if defined(__cplusplus) extern "C" { // Prevents name mangling of functions #endif // Window-related functions RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context RLAPI void CloseWindow(void); // Close window and unload OpenGL context RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen RLAPI bool IsWindowHidden(void); // Check if window is currently hidden RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized RLAPI bool IsWindowFocused(void); // Check if window is currently focused RLAPI bool IsWindowResized(void); // Check if window has been resized last frame RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) RLAPI void SetWindowTitle(const char *title); // Set title for window RLAPI void SetWindowPosition(int x, int y); // Set window position on screen RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) RLAPI void SetWindowSize(int width, int height); // Set window dimensions RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] RLAPI void SetWindowFocused(void); // Set window focused RLAPI void *GetWindowHandle(void); // Get native window handle RLAPI int GetScreenWidth(void); // Get current screen width RLAPI int GetScreenHeight(void); // Get current screen height RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) RLAPI int GetMonitorCount(void); // Get number of connected monitors RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor RLAPI void SetClipboardText(const char *text); // Set clipboard text content RLAPI const char *GetClipboardText(void); // Get clipboard text content RLAPI Image GetClipboardImage(void); // Get clipboard image content RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling // Cursor-related functions RLAPI void ShowCursor(void); // Shows cursor RLAPI void HideCursor(void); // Hides cursor RLAPI bool IsCursorHidden(void); // Check if cursor is not visible RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) RLAPI void DisableCursor(void); // Disables cursor (lock cursor) RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen // Drawing-related functions RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) RLAPI void EndMode2D(void); // Ends 2D mode with custom camera RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture RLAPI void EndTextureMode(void); // Ends drawing to render texture RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) RLAPI void EndScissorMode(void); // End scissor mode RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) // VR stereo config functions for VR simulator RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config // Shader management functions // NOTE: Shader functionality is not available on OpenGL 1.1 RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU) RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) // Screen-space-related functions #define GetMouseRay GetScreenToWorldRay // Compatibility hack for previous raylib versions RLAPI Ray GetScreenToWorldRay(Vector2 position, Camera camera); // Get a ray trace from screen position (i.e mouse) RLAPI Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height); // Get a ray trace from screen position (i.e mouse) in a viewport RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get size position for a 3d world space position RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get the screen space position for a 2d camera world space position RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen space position RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix // Timing-related functions RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() RLAPI int GetFPS(void); // Get current FPS // Custom frame control functions // NOTE: Those functions are intended for advanced users that want full control over the frame processing // By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() // To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) RLAPI void PollInputEvents(void); // Register all input events RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) // Random values generation functions RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence // Misc. functions RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) // NOTE: Following functions implemented in module [utils] //------------------------------------------------------------------ RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator RLAPI void MemFree(void *ptr); // Internal memory free // Set custom callbacks // WARNING: Callbacks setup is intended for advanced users RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver // Files management functions RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write), returns true on success RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success //------------------------------------------------------------------ // File system functions RLAPI bool FileExists(const char *fileName); // Check if file exists RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (including point: .png, .wav) RLAPI int GetFileLength(const char *fileName); // Get file length in bytes (NOTE: GetFileSize() conflicts with windows.h) RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) // Compression/Encoding functionality RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree() RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) // Automation events functionality RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) RLAPI void StopAutomationEventRecording(void); // Stop recording automation events RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event //------------------------------------------------------------------------------------ // Input Handling Functions (Module: core) //------------------------------------------------------------------------------------ // Input-related functions: keyboard RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again RLAPI bool IsKeyDown(int key); // Check if a key is being pressed RLAPI bool IsKeyReleased(int key); // Check if a key has been released once RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) // Input-related functions: gamepads RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) // Input-related functions: mouse RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once RLAPI bool IsMouseButtonDown(int button); // Check if a mouse button is being pressed RLAPI bool IsMouseButtonReleased(int button); // Check if a mouse button has been released once RLAPI bool IsMouseButtonUp(int button); // Check if a mouse button is NOT being pressed RLAPI int GetMouseX(void); // Get mouse position X RLAPI int GetMouseY(void); // Get mouse position Y RLAPI Vector2 GetMousePosition(void); // Get mouse position XY RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames RLAPI void SetMousePosition(int x, int y); // Set mouse position XY RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement for X or Y, whichever is larger RLAPI Vector2 GetMouseWheelMoveV(void); // Get mouse wheel movement for both X and Y RLAPI void SetMouseCursor(int cursor); // Set mouse cursor // Input-related functions: touch RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index RLAPI int GetTouchPointCount(void); // Get number of touch points //------------------------------------------------------------------------------------ // Gestures and Touch Handling Functions (Module: rgestures) //------------------------------------------------------------------------------------ RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected RLAPI int GetGestureDetected(void); // Get latest detected gesture RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector RLAPI float GetGestureDragAngle(void); // Get gesture drag angle RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle //------------------------------------------------------------------------------------ // Camera System Functions (Module: rcamera) //------------------------------------------------------------------------------------ RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation //------------------------------------------------------------------------------------ // Basic Shapes Drawing Functions (Module: shapes) //------------------------------------------------------------------------------------ // Set texture and rectangle to be used on shapes drawing // NOTE: It can be useful when using basic shapes and one single font, // defining a font char white rectangle would allow drawing everything in a single draw call RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing // Basic shapes drawing functions RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) RLAPI void DrawLineStrip(const Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle lines with rounded edges RLAPI void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters // Splines drawing functions RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points // Spline segment point evaluation functions, for a given t [0.0f .. 1.0f] RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom RLAPI Vector2 GetSplinePointBezierQuad(Vector2 p1, Vector2 c2, Vector2 p3, float t); // Get (evaluate) spline point: Quadratic Bezier RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float t); // Get (evaluate) spline point: Cubic Bezier // Basic shapes collision detection functions RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision //------------------------------------------------------------------------------------ // Texture Loading and Drawing Functions (Module: textures) //------------------------------------------------------------------------------------ // Image loading functions // NOTE: These functions do not require GPU access RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success // Image generation functions RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color RLAPI Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end); // Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient RLAPI Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer); // Generate image: square gradient RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells RLAPI Image GenImageText(int width, int height, const char *text); // Generate image: grayscale image from text data // Image manipulation functions RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece RLAPI Image ImageFromChannel(Image image, int selectedChannel); // Create an image from a selected channel of another image (GRAYSCALE) RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) RLAPI void ImageFlipVertical(Image *image); // Flip image vertically RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally RLAPI void ImageRotate(Image *image, int degrees); // Rotate image by input angle in degrees (-359 to 359) RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint RLAPI void ImageColorInvert(Image *image); // Modify image color: invert RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position // Image drawing functions // NOTE: Image software-rendering functions (CPU) RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) RLAPI void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color); // Draw a line defining thickness within an image RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw a filled circle within an image RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw a filled circle within an image (Vector version) RLAPI void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle outline within an image RLAPI void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color); // Draw circle outline within an image (Vector version) RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) // Texture loading functions // NOTE: These functions require GPU access RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU) RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data // Texture configuration functions RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode // Texture drawing functions RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely // Color/pixel related functions RLAPI bool ColorIsEqual(Color col1, Color col2); // Check if two colors are equal RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color (0xRRGGBBAA) RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] RLAPI Color ColorTint(Color color, Color tint); // Get color multiplied with another color RLAPI Color ColorBrightness(Color color, float factor); // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format //------------------------------------------------------------------------------------ // Font Loading and Text Drawing Functions (Module: text) //------------------------------------------------------------------------------------ // Font loading/unloading functions RLAPI Font GetFontDefault(void); // Get the default Font RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked) RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount, int type); // Load font data for further use RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) RLAPI void UnloadFont(Font font); // Unload font from GPU memory (VRAM) RLAPI bool ExportFontAsCode(Font font, const char *fileName); // Export font as code file, returns true on success // Text drawing functions RLAPI void DrawFPS(int posX, int posY); // Draw current FPS RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint) // Text font info functions RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found // Text codepoints management functions (unicode characters) RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) // Text strings management functions (no UTF-8 strings, only byte chars) // NOTE: Some strings allocate memory internally for returned strings, just be careful! RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string RLAPI char *TextReplace(const char *text, const char *replace, const char *by); // Replace text string (WARNING: memory must be freed!) RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string RLAPI const char *TextToSnake(const char *text); // Get Snake case notation version of provided string RLAPI const char *TextToCamel(const char *text); // Get Camel case notation version of provided string RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) RLAPI float TextToFloat(const char *text); // Get float value from text (negative values not supported) //------------------------------------------------------------------------------------ // Basic 3d Shapes Drawing Functions (Module: models) //------------------------------------------------------------------------------------ // Basic geometric 3D shapes drawing functions RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) RLAPI void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos RLAPI void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw a capsule with the center of its sphere caps at startPos and endPos RLAPI void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw capsule wireframe with the center of its sphere caps at startPos and endPos RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) //------------------------------------------------------------------------------------ // Model 3d Loading and Drawing Functions (Module: models) //------------------------------------------------------------------------------------ // Model management functions RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs) RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) // Model drawing functions RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation // Mesh management functions RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids RLAPI void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success RLAPI bool ExportMeshAsCode(Mesh mesh, const char *fileName); // Export mesh as code file (.h) defining multiple arrays of vertex attributes // Mesh generation functions RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data // Material loading/unloading functions RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU) RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh // Model animations loading/unloading functions RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match // Collision detection functions RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad //------------------------------------------------------------------------------------ // Audio Loading and Playing Functions (Module: audio) //------------------------------------------------------------------------------------ typedef void (*AudioCallback)(void *bufferData, unsigned int frames); // Audio device management functions RLAPI void InitAudioDevice(void); // Initialize audio device and context RLAPI void CloseAudioDevice(void); // Close the audio device and context RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully RLAPI void SetMasterVolume(float volume); // Set master volume (listener) RLAPI float GetMasterVolume(void); // Get master volume (listener) // Wave/Sound loading/unloading functions RLAPI Wave LoadWave(const char *fileName); // Load wave data from file RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters) RLAPI Sound LoadSound(const char *fileName); // Load sound from file RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data RLAPI void UnloadWave(Wave wave); // Unload wave data RLAPI void UnloadSound(Sound sound); // Unload sound RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success // Wave/Sound management functions RLAPI void PlaySound(Sound sound); // Play a sound RLAPI void StopSound(Sound sound); // Stop playing a sound RLAPI void PauseSound(Sound sound); // Pause a sound RLAPI void ResumeSound(Sound sound); // Resume a paused sound RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (0.5 is center) RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a 32bit float data array RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() // Music management functions RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized) RLAPI void UnloadMusicStream(Music music); // Unload music stream RLAPI void PlayMusicStream(Music music); // Start music playing RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming RLAPI void StopMusicStream(Music music); // Stop music playing RLAPI void PauseMusicStream(Music music); // Pause music playing RLAPI void ResumeMusicStream(Music music); // Resume playing paused music RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (0.5 is center) RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) // AudioStream management functions RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized) RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan for audio stream (0.5 is centered) RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives the samples as 'float' RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline #if defined(__cplusplus) } #endif #endif // RAYLIB_H ================================================ FILE: renderers/raylib/raymath.h ================================================ /********************************************************************************************** * * raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions * * CONVENTIONS: * - Matrix structure is defined as row-major (memory layout) but parameters naming AND all * math operations performed by the library consider the structure as it was column-major * It is like transposed versions of the matrices are used for all the maths * It benefits some functions making them cache-friendly and also avoids matrix * transpositions sometimes required by OpenGL * Example: In memory order, row0 is [m0 m4 m8 m12] but in semantic math row0 is [m0 m1 m2 m3] * - Functions are always self-contained, no function use another raymath function inside, * required code is directly re-implemented inside * - Functions input parameters are always received by value (2 unavoidable exceptions) * - Functions use always a "result" variable for return (except C++ operators) * - Functions are always defined inline * - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) * - No compound literals used to make sure libray is compatible with C++ * * CONFIGURATION: * #define RAYMATH_IMPLEMENTATION * Generates the implementation of the library into the included file. * If not defined, the library is in header only mode and can be included in other headers * or source files without problems. But only ONE file should hold the implementation. * * #define RAYMATH_STATIC_INLINE * Define static inline functions code, so #include header suffices for use. * This may use up lots of memory. * * #define RAYMATH_DISABLE_CPP_OPERATORS * Disables C++ operator overloads for raymath types. * * LICENSE: zlib/libpng * * Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, including commercial * applications, and to alter it and redistribute it freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not claim that you * wrote the original software. If you use this software in a product, an acknowledgment * in the product documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be misrepresented * as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * **********************************************************************************************/ #ifndef RAYMATH_H #define RAYMATH_H #if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" #endif // Function specifiers definition #if defined(RAYMATH_IMPLEMENTATION) #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll) #elif defined(BUILD_LIBTYPE_SHARED) #define RMAPI __attribute__((visibility("default"))) // We are building raylib as a Unix shared library (.so/.dylib) #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) #else #define RMAPI extern inline // Provide external definition #endif #elif defined(RAYMATH_STATIC_INLINE) #define RMAPI static inline // Functions may be inlined, no external out-of-line definition #else #if defined(__TINYC__) #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) #else #define RMAPI inline // Functions may be inlined or external definition used #endif #endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #ifndef PI #define PI 3.14159265358979323846f #endif #ifndef EPSILON #define EPSILON 0.000001f #endif #ifndef DEG2RAD #define DEG2RAD (PI/180.0f) #endif #ifndef RAD2DEG #define RAD2DEG (180.0f/PI) #endif // Get float vector for Matrix #ifndef MatrixToFloat #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) #endif // Get float vector for Vector3 #ifndef Vector3ToFloat #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) #endif //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- #if !defined(RL_VECTOR2_TYPE) // Vector2 type typedef struct Vector2 { float x; float y; } Vector2; #define RL_VECTOR2_TYPE #endif #if !defined(RL_VECTOR3_TYPE) // Vector3 type typedef struct Vector3 { float x; float y; float z; } Vector3; #define RL_VECTOR3_TYPE #endif #if !defined(RL_VECTOR4_TYPE) // Vector4 type typedef struct Vector4 { float x; float y; float z; float w; } Vector4; #define RL_VECTOR4_TYPE #endif #if !defined(RL_QUATERNION_TYPE) // Quaternion type typedef Vector4 Quaternion; #define RL_QUATERNION_TYPE #endif #if !defined(RL_MATRIX_TYPE) // Matrix type (OpenGL style 4x4 - right handed, column major) typedef struct Matrix { float m0, m4, m8, m12; // Matrix first row (4 components) float m1, m5, m9, m13; // Matrix second row (4 components) float m2, m6, m10, m14; // Matrix third row (4 components) float m3, m7, m11, m15; // Matrix fourth row (4 components) } Matrix; #define RL_MATRIX_TYPE #endif // NOTE: Helper types to be used instead of array return types for *ToFloat functions typedef struct float3 { float v[3]; } float3; typedef struct float16 { float v[16]; } float16; #include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() //---------------------------------------------------------------------------------- // Module Functions Definition - Utils math //---------------------------------------------------------------------------------- // Clamp float value RMAPI float Clamp(float value, float min, float max) { float result = (value < min)? min : value; if (result > max) result = max; return result; } // Calculate linear interpolation between two floats RMAPI float Lerp(float start, float end, float amount) { float result = start + amount*(end - start); return result; } // Normalize input value within input range RMAPI float Normalize(float value, float start, float end) { float result = (value - start)/(end - start); return result; } // Remap input value within input range to output range RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) { float result = (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; return result; } // Wrap input value from min to max RMAPI float Wrap(float value, float min, float max) { float result = value - (max - min)*floorf((value - min)/(max - min)); return result; } // Check whether two given floats are almost equal RMAPI int FloatEquals(float x, float y) { #if !defined(EPSILON) #define EPSILON 0.000001f #endif int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); return result; } //---------------------------------------------------------------------------------- // Module Functions Definition - Vector2 math //---------------------------------------------------------------------------------- // Vector with components value 0.0f RMAPI Vector2 Vector2Zero(void) { Vector2 result = { 0.0f, 0.0f }; return result; } // Vector with components value 1.0f RMAPI Vector2 Vector2One(void) { Vector2 result = { 1.0f, 1.0f }; return result; } // Add two vectors (v1 + v2) RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) { Vector2 result = { v1.x + v2.x, v1.y + v2.y }; return result; } // Add vector and float value RMAPI Vector2 Vector2AddValue(Vector2 v, float add) { Vector2 result = { v.x + add, v.y + add }; return result; } // Subtract two vectors (v1 - v2) RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) { Vector2 result = { v1.x - v2.x, v1.y - v2.y }; return result; } // Subtract vector by float value RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) { Vector2 result = { v.x - sub, v.y - sub }; return result; } // Calculate vector length RMAPI float Vector2Length(Vector2 v) { float result = sqrtf((v.x*v.x) + (v.y*v.y)); return result; } // Calculate vector square length RMAPI float Vector2LengthSqr(Vector2 v) { float result = (v.x*v.x) + (v.y*v.y); return result; } // Calculate two vectors dot product RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) { float result = (v1.x*v2.x + v1.y*v2.y); return result; } // Calculate distance between two vectors RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) { float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); return result; } // Calculate square distance between two vectors RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) { float result = ((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); return result; } // Calculate angle between two vectors // NOTE: Angle is calculated from origin point (0, 0) RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) { float result = 0.0f; float dot = v1.x*v2.x + v1.y*v2.y; float det = v1.x*v2.y - v1.y*v2.x; result = atan2f(det, dot); return result; } // Calculate angle defined by a two vectors line // NOTE: Parameters need to be normalized // Current implementation should be aligned with glm::angle RMAPI float Vector2LineAngle(Vector2 start, Vector2 end) { float result = 0.0f; // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior result = -atan2f(end.y - start.y, end.x - start.x); return result; } // Scale vector (multiply by value) RMAPI Vector2 Vector2Scale(Vector2 v, float scale) { Vector2 result = { v.x*scale, v.y*scale }; return result; } // Multiply vector by vector RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) { Vector2 result = { v1.x*v2.x, v1.y*v2.y }; return result; } // Negate vector RMAPI Vector2 Vector2Negate(Vector2 v) { Vector2 result = { -v.x, -v.y }; return result; } // Divide vector by vector RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) { Vector2 result = { v1.x/v2.x, v1.y/v2.y }; return result; } // Normalize provided vector RMAPI Vector2 Vector2Normalize(Vector2 v) { Vector2 result = { 0 }; float length = sqrtf((v.x*v.x) + (v.y*v.y)); if (length > 0) { float ilength = 1.0f/length; result.x = v.x*ilength; result.y = v.y*ilength; } return result; } // Transforms a Vector2 by a given Matrix RMAPI Vector2 Vector2Transform(Vector2 v, Matrix mat) { Vector2 result = { 0 }; float x = v.x; float y = v.y; float z = 0; result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; return result; } // Calculate linear interpolation between two vectors RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) { Vector2 result = { 0 }; result.x = v1.x + amount*(v2.x - v1.x); result.y = v1.y + amount*(v2.y - v1.y); return result; } // Calculate reflected vector to normal RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) { Vector2 result = { 0 }; float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product result.x = v.x - (2.0f*normal.x)*dotProduct; result.y = v.y - (2.0f*normal.y)*dotProduct; return result; } // Get min value for each pair of components RMAPI Vector2 Vector2Min(Vector2 v1, Vector2 v2) { Vector2 result = { 0 }; result.x = fminf(v1.x, v2.x); result.y = fminf(v1.y, v2.y); return result; } // Get max value for each pair of components RMAPI Vector2 Vector2Max(Vector2 v1, Vector2 v2) { Vector2 result = { 0 }; result.x = fmaxf(v1.x, v2.x); result.y = fmaxf(v1.y, v2.y); return result; } // Rotate vector by angle RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) { Vector2 result = { 0 }; float cosres = cosf(angle); float sinres = sinf(angle); result.x = v.x*cosres - v.y*sinres; result.y = v.x*sinres + v.y*cosres; return result; } // Move Vector towards target RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) { Vector2 result = { 0 }; float dx = target.x - v.x; float dy = target.y - v.y; float value = (dx*dx) + (dy*dy); if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; float dist = sqrtf(value); result.x = v.x + dx/dist*maxDistance; result.y = v.y + dy/dist*maxDistance; return result; } // Invert the given vector RMAPI Vector2 Vector2Invert(Vector2 v) { Vector2 result = { 1.0f/v.x, 1.0f/v.y }; return result; } // Clamp the components of the vector between // min and max values specified by the given vectors RMAPI Vector2 Vector2Clamp(Vector2 v, Vector2 min, Vector2 max) { Vector2 result = { 0 }; result.x = fminf(max.x, fmaxf(min.x, v.x)); result.y = fminf(max.y, fmaxf(min.y, v.y)); return result; } // Clamp the magnitude of the vector between two min and max values RMAPI Vector2 Vector2ClampValue(Vector2 v, float min, float max) { Vector2 result = v; float length = (v.x*v.x) + (v.y*v.y); if (length > 0.0f) { length = sqrtf(length); float scale = 1; // By default, 1 as the neutral element. if (length < min) { scale = min/length; } else if (length > max) { scale = max/length; } result.x = v.x*scale; result.y = v.y*scale; } return result; } // Check whether two given vectors are almost equal RMAPI int Vector2Equals(Vector2 p, Vector2 q) { #if !defined(EPSILON) #define EPSILON 0.000001f #endif int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))); return result; } // Compute the direction of a refracted ray // v: normalized direction of the incoming ray // n: normalized normal vector of the interface of two optical media // r: ratio of the refractive index of the medium from where the ray comes // to the refractive index of the medium on the other side of the surface RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) { Vector2 result = { 0 }; float dot = v.x*n.x + v.y*n.y; float d = 1.0f - r*r*(1.0f - dot*dot); if (d >= 0.0f) { d = sqrtf(d); v.x = r*v.x - (r*dot + d)*n.x; v.y = r*v.y - (r*dot + d)*n.y; result = v; } return result; } //---------------------------------------------------------------------------------- // Module Functions Definition - Vector3 math //---------------------------------------------------------------------------------- // Vector with components value 0.0f RMAPI Vector3 Vector3Zero(void) { Vector3 result = { 0.0f, 0.0f, 0.0f }; return result; } // Vector with components value 1.0f RMAPI Vector3 Vector3One(void) { Vector3 result = { 1.0f, 1.0f, 1.0f }; return result; } // Add two vectors RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) { Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; return result; } // Add vector and float value RMAPI Vector3 Vector3AddValue(Vector3 v, float add) { Vector3 result = { v.x + add, v.y + add, v.z + add }; return result; } // Subtract two vectors RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) { Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; return result; } // Subtract vector by float value RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) { Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; return result; } // Multiply vector by scalar RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) { Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; return result; } // Multiply vector by vector RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) { Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; return result; } // Calculate two vectors cross product RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) { Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; return result; } // Calculate one vector perpendicular vector RMAPI Vector3 Vector3Perpendicular(Vector3 v) { Vector3 result = { 0 }; float min = fabsf(v.x); Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; if (fabsf(v.y) < min) { min = fabsf(v.y); Vector3 tmp = {0.0f, 1.0f, 0.0f}; cardinalAxis = tmp; } if (fabsf(v.z) < min) { Vector3 tmp = {0.0f, 0.0f, 1.0f}; cardinalAxis = tmp; } // Cross product between vectors result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; return result; } // Calculate vector length RMAPI float Vector3Length(const Vector3 v) { float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); return result; } // Calculate vector square length RMAPI float Vector3LengthSqr(const Vector3 v) { float result = v.x*v.x + v.y*v.y + v.z*v.z; return result; } // Calculate two vectors dot product RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) { float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); return result; } // Calculate distance between two vectors RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) { float result = 0.0f; float dx = v2.x - v1.x; float dy = v2.y - v1.y; float dz = v2.z - v1.z; result = sqrtf(dx*dx + dy*dy + dz*dz); return result; } // Calculate square distance between two vectors RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) { float result = 0.0f; float dx = v2.x - v1.x; float dy = v2.y - v1.y; float dz = v2.z - v1.z; result = dx*dx + dy*dy + dz*dz; return result; } // Calculate angle between two vectors RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) { float result = 0.0f; Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); result = atan2f(len, dot); return result; } // Negate provided vector (invert direction) RMAPI Vector3 Vector3Negate(Vector3 v) { Vector3 result = { -v.x, -v.y, -v.z }; return result; } // Divide vector by vector RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) { Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; return result; } // Normalize provided vector RMAPI Vector3 Vector3Normalize(Vector3 v) { Vector3 result = v; float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); if (length != 0.0f) { float ilength = 1.0f/length; result.x *= ilength; result.y *= ilength; result.z *= ilength; } return result; } //Calculate the projection of the vector v1 on to v2 RMAPI Vector3 Vector3Project(Vector3 v1, Vector3 v2) { Vector3 result = { 0 }; float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); float mag = v1dv2/v2dv2; result.x = v2.x*mag; result.y = v2.y*mag; result.z = v2.z*mag; return result; } //Calculate the rejection of the vector v1 on to v2 RMAPI Vector3 Vector3Reject(Vector3 v1, Vector3 v2) { Vector3 result = { 0 }; float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); float mag = v1dv2/v2dv2; result.x = v1.x - (v2.x*mag); result.y = v1.y - (v2.y*mag); result.z = v1.z - (v2.z*mag); return result; } // Orthonormalize provided vectors // Makes vectors normalized and orthogonal to each other // Gram-Schmidt function implementation RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) { float length = 0.0f; float ilength = 0.0f; // Vector3Normalize(*v1); Vector3 v = *v1; length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); if (length == 0.0f) length = 1.0f; ilength = 1.0f/length; v1->x *= ilength; v1->y *= ilength; v1->z *= ilength; // Vector3CrossProduct(*v1, *v2) Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; // Vector3Normalize(vn1); v = vn1; length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); if (length == 0.0f) length = 1.0f; ilength = 1.0f/length; vn1.x *= ilength; vn1.y *= ilength; vn1.z *= ilength; // Vector3CrossProduct(vn1, *v1) Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; *v2 = vn2; } // Transforms a Vector3 by a given Matrix RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) { Vector3 result = { 0 }; float x = v.x; float y = v.y; float z = v.z; result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; return result; } // Transform a vector by quaternion rotation RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) { Vector3 result = { 0 }; result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); return result; } // Rotates a vector around an axis RMAPI Vector3 Vector3RotateByAxisAngle(Vector3 v, Vector3 axis, float angle) { // Using Euler-Rodrigues Formula // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula Vector3 result = v; // Vector3Normalize(axis); float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); if (length == 0.0f) length = 1.0f; float ilength = 1.0f/length; axis.x *= ilength; axis.y *= ilength; axis.z *= ilength; angle /= 2.0f; float a = sinf(angle); float b = axis.x*a; float c = axis.y*a; float d = axis.z*a; a = cosf(angle); Vector3 w = { b, c, d }; // Vector3CrossProduct(w, v) Vector3 wv = { w.y*v.z - w.z*v.y, w.z*v.x - w.x*v.z, w.x*v.y - w.y*v.x }; // Vector3CrossProduct(w, wv) Vector3 wwv = { w.y*wv.z - w.z*wv.y, w.z*wv.x - w.x*wv.z, w.x*wv.y - w.y*wv.x }; // Vector3Scale(wv, 2*a) a *= 2; wv.x *= a; wv.y *= a; wv.z *= a; // Vector3Scale(wwv, 2) wwv.x *= 2; wwv.y *= 2; wwv.z *= 2; result.x += wv.x; result.y += wv.y; result.z += wv.z; result.x += wwv.x; result.y += wwv.y; result.z += wwv.z; return result; } // Move Vector towards target RMAPI Vector3 Vector3MoveTowards(Vector3 v, Vector3 target, float maxDistance) { Vector3 result = { 0 }; float dx = target.x - v.x; float dy = target.y - v.y; float dz = target.z - v.z; float value = (dx*dx) + (dy*dy) + (dz*dz); if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; float dist = sqrtf(value); result.x = v.x + dx/dist*maxDistance; result.y = v.y + dy/dist*maxDistance; result.z = v.z + dz/dist*maxDistance; return result; } // Calculate linear interpolation between two vectors RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) { Vector3 result = { 0 }; result.x = v1.x + amount*(v2.x - v1.x); result.y = v1.y + amount*(v2.y - v1.y); result.z = v1.z + amount*(v2.z - v1.z); return result; } // Calculate cubic hermite interpolation between two vectors and their tangents // as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount) { Vector3 result = { 0 }; float amountPow2 = amount*amount; float amountPow3 = amount*amount*amount; result.x = (2*amountPow3 - 3*amountPow2 + 1)*v1.x + (amountPow3 - 2*amountPow2 + amount)*tangent1.x + (-2*amountPow3 + 3*amountPow2)*v2.x + (amountPow3 - amountPow2)*tangent2.x; result.y = (2*amountPow3 - 3*amountPow2 + 1)*v1.y + (amountPow3 - 2*amountPow2 + amount)*tangent1.y + (-2*amountPow3 + 3*amountPow2)*v2.y + (amountPow3 - amountPow2)*tangent2.y; result.z = (2*amountPow3 - 3*amountPow2 + 1)*v1.z + (amountPow3 - 2*amountPow2 + amount)*tangent1.z + (-2*amountPow3 + 3*amountPow2)*v2.z + (amountPow3 - amountPow2)*tangent2.z; return result; } // Calculate reflected vector to normal RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) { Vector3 result = { 0 }; // I is the original vector // N is the normal of the incident plane // R = I - (2*N*(DotProduct[I, N])) float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); result.x = v.x - (2.0f*normal.x)*dotProduct; result.y = v.y - (2.0f*normal.y)*dotProduct; result.z = v.z - (2.0f*normal.z)*dotProduct; return result; } // Get min value for each pair of components RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) { Vector3 result = { 0 }; result.x = fminf(v1.x, v2.x); result.y = fminf(v1.y, v2.y); result.z = fminf(v1.z, v2.z); return result; } // Get max value for each pair of components RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) { Vector3 result = { 0 }; result.x = fmaxf(v1.x, v2.x); result.y = fmaxf(v1.y, v2.y); result.z = fmaxf(v1.z, v2.z); return result; } // Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) // NOTE: Assumes P is on the plane of the triangle RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) { Vector3 result = { 0 }; Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) float denom = d00*d11 - d01*d01; result.y = (d11*d20 - d01*d21)/denom; result.z = (d00*d21 - d01*d20)/denom; result.x = 1.0f - (result.z + result.y); return result; } // Projects a Vector3 from screen space into object space // NOTE: We are avoiding calling other raymath functions despite available RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) { Vector3 result = { 0 }; // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it Matrix matViewProj = { // MatrixMultiply(view, projection); view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; // Calculate inverted matrix -> MatrixInvert(matViewProj); // Cache the matrix values (speed optimization) float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; float b00 = a00*a11 - a01*a10; float b01 = a00*a12 - a02*a10; float b02 = a00*a13 - a03*a10; float b03 = a01*a12 - a02*a11; float b04 = a01*a13 - a03*a11; float b05 = a02*a13 - a03*a12; float b06 = a20*a31 - a21*a30; float b07 = a20*a32 - a22*a30; float b08 = a20*a33 - a23*a30; float b09 = a21*a32 - a22*a31; float b10 = a21*a33 - a23*a31; float b11 = a22*a33 - a23*a32; // Calculate the invert determinant (inlined to avoid double-caching) float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); Matrix matViewProjInv = { (a11*b11 - a12*b10 + a13*b09)*invDet, (-a01*b11 + a02*b10 - a03*b09)*invDet, (a31*b05 - a32*b04 + a33*b03)*invDet, (-a21*b05 + a22*b04 - a23*b03)*invDet, (-a10*b11 + a12*b08 - a13*b07)*invDet, (a00*b11 - a02*b08 + a03*b07)*invDet, (-a30*b05 + a32*b02 - a33*b01)*invDet, (a20*b05 - a22*b02 + a23*b01)*invDet, (a10*b10 - a11*b08 + a13*b06)*invDet, (-a00*b10 + a01*b08 - a03*b06)*invDet, (a30*b04 - a31*b02 + a33*b00)*invDet, (-a20*b04 + a21*b02 - a23*b00)*invDet, (-a10*b09 + a11*b07 - a12*b06)*invDet, (a00*b09 - a01*b07 + a02*b06)*invDet, (-a30*b03 + a31*b01 - a32*b00)*invDet, (a20*b03 - a21*b01 + a22*b00)*invDet }; // Create quaternion from source point Quaternion quat = { source.x, source.y, source.z, 1.0f }; // Multiply quat point by unprojecte matrix Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; // Normalized world points in vectors result.x = qtransformed.x/qtransformed.w; result.y = qtransformed.y/qtransformed.w; result.z = qtransformed.z/qtransformed.w; return result; } // Get Vector3 as float array RMAPI float3 Vector3ToFloatV(Vector3 v) { float3 buffer = { 0 }; buffer.v[0] = v.x; buffer.v[1] = v.y; buffer.v[2] = v.z; return buffer; } // Invert the given vector RMAPI Vector3 Vector3Invert(Vector3 v) { Vector3 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z }; return result; } // Clamp the components of the vector between // min and max values specified by the given vectors RMAPI Vector3 Vector3Clamp(Vector3 v, Vector3 min, Vector3 max) { Vector3 result = { 0 }; result.x = fminf(max.x, fmaxf(min.x, v.x)); result.y = fminf(max.y, fmaxf(min.y, v.y)); result.z = fminf(max.z, fmaxf(min.z, v.z)); return result; } // Clamp the magnitude of the vector between two values RMAPI Vector3 Vector3ClampValue(Vector3 v, float min, float max) { Vector3 result = v; float length = (v.x*v.x) + (v.y*v.y) + (v.z*v.z); if (length > 0.0f) { length = sqrtf(length); float scale = 1; // By default, 1 as the neutral element. if (length < min) { scale = min/length; } else if (length > max) { scale = max/length; } result.x = v.x*scale; result.y = v.y*scale; result.z = v.z*scale; } return result; } // Check whether two given vectors are almost equal RMAPI int Vector3Equals(Vector3 p, Vector3 q) { #if !defined(EPSILON) #define EPSILON 0.000001f #endif int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))); return result; } // Compute the direction of a refracted ray // v: normalized direction of the incoming ray // n: normalized normal vector of the interface of two optical media // r: ratio of the refractive index of the medium from where the ray comes // to the refractive index of the medium on the other side of the surface RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) { Vector3 result = { 0 }; float dot = v.x*n.x + v.y*n.y + v.z*n.z; float d = 1.0f - r*r*(1.0f - dot*dot); if (d >= 0.0f) { d = sqrtf(d); v.x = r*v.x - (r*dot + d)*n.x; v.y = r*v.y - (r*dot + d)*n.y; v.z = r*v.z - (r*dot + d)*n.z; result = v; } return result; } //---------------------------------------------------------------------------------- // Module Functions Definition - Vector4 math //---------------------------------------------------------------------------------- RMAPI Vector4 Vector4Zero(void) { Vector4 result = { 0.0f, 0.0f, 0.0f, 0.0f }; return result; } RMAPI Vector4 Vector4One(void) { Vector4 result = { 1.0f, 1.0f, 1.0f, 1.0f }; return result; } RMAPI Vector4 Vector4Add(Vector4 v1, Vector4 v2) { Vector4 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z, v1.w + v2.w }; return result; } RMAPI Vector4 Vector4AddValue(Vector4 v, float add) { Vector4 result = { v.x + add, v.y + add, v.z + add, v.w + add }; return result; } RMAPI Vector4 Vector4Subtract(Vector4 v1, Vector4 v2) { Vector4 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z, v1.w - v2.w }; return result; } RMAPI Vector4 Vector4SubtractValue(Vector4 v, float add) { Vector4 result = { v.x - add, v.y - add, v.z - add, v.w - add }; return result; } RMAPI float Vector4Length(Vector4 v) { float result = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); return result; } RMAPI float Vector4LengthSqr(Vector4 v) { float result = (v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w); return result; } RMAPI float Vector4DotProduct(Vector4 v1, Vector4 v2) { float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w); return result; } // Calculate distance between two vectors RMAPI float Vector4Distance(Vector4 v1, Vector4 v2) { float result = sqrtf( (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w)); return result; } // Calculate square distance between two vectors RMAPI float Vector4DistanceSqr(Vector4 v1, Vector4 v2) { float result = (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w); return result; } RMAPI Vector4 Vector4Scale(Vector4 v, float scale) { Vector4 result = { v.x*scale, v.y*scale, v.z*scale, v.w*scale }; return result; } // Multiply vector by vector RMAPI Vector4 Vector4Multiply(Vector4 v1, Vector4 v2) { Vector4 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z, v1.w*v2.w }; return result; } // Negate vector RMAPI Vector4 Vector4Negate(Vector4 v) { Vector4 result = { -v.x, -v.y, -v.z, -v.w }; return result; } // Divide vector by vector RMAPI Vector4 Vector4Divide(Vector4 v1, Vector4 v2) { Vector4 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z, v1.w/v2.w }; return result; } // Normalize provided vector RMAPI Vector4 Vector4Normalize(Vector4 v) { Vector4 result = { 0 }; float length = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); if (length > 0) { float ilength = 1.0f/length; result.x = v.x*ilength; result.y = v.y*ilength; result.z = v.z*ilength; result.w = v.w*ilength; } return result; } // Get min value for each pair of components RMAPI Vector4 Vector4Min(Vector4 v1, Vector4 v2) { Vector4 result = { 0 }; result.x = fminf(v1.x, v2.x); result.y = fminf(v1.y, v2.y); result.z = fminf(v1.z, v2.z); result.w = fminf(v1.w, v2.w); return result; } // Get max value for each pair of components RMAPI Vector4 Vector4Max(Vector4 v1, Vector4 v2) { Vector4 result = { 0 }; result.x = fmaxf(v1.x, v2.x); result.y = fmaxf(v1.y, v2.y); result.z = fmaxf(v1.z, v2.z); result.w = fmaxf(v1.w, v2.w); return result; } // Calculate linear interpolation between two vectors RMAPI Vector4 Vector4Lerp(Vector4 v1, Vector4 v2, float amount) { Vector4 result = { 0 }; result.x = v1.x + amount*(v2.x - v1.x); result.y = v1.y + amount*(v2.y - v1.y); result.z = v1.z + amount*(v2.z - v1.z); result.w = v1.w + amount*(v2.w - v1.w); return result; } // Move Vector towards target RMAPI Vector4 Vector4MoveTowards(Vector4 v, Vector4 target, float maxDistance) { Vector4 result = { 0 }; float dx = target.x - v.x; float dy = target.y - v.y; float dz = target.z - v.z; float dw = target.w - v.w; float value = (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw); if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; float dist = sqrtf(value); result.x = v.x + dx/dist*maxDistance; result.y = v.y + dy/dist*maxDistance; result.z = v.z + dz/dist*maxDistance; result.w = v.w + dw/dist*maxDistance; return result; } // Invert the given vector RMAPI Vector4 Vector4Invert(Vector4 v) { Vector4 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z, 1.0f/v.w }; return result; } // Check whether two given vectors are almost equal RMAPI int Vector4Equals(Vector4 p, Vector4 q) { #if !defined(EPSILON) #define EPSILON 0.000001f #endif int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w))))); return result; } //---------------------------------------------------------------------------------- // Module Functions Definition - Matrix math //---------------------------------------------------------------------------------- // Compute matrix determinant RMAPI float MatrixDeterminant(Matrix mat) { float result = 0.0f; // Cache the matrix values (speed optimization) float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; return result; } // Get the trace of the matrix (sum of the values along the diagonal) RMAPI float MatrixTrace(Matrix mat) { float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); return result; } // Transposes provided matrix RMAPI Matrix MatrixTranspose(Matrix mat) { Matrix result = { 0 }; result.m0 = mat.m0; result.m1 = mat.m4; result.m2 = mat.m8; result.m3 = mat.m12; result.m4 = mat.m1; result.m5 = mat.m5; result.m6 = mat.m9; result.m7 = mat.m13; result.m8 = mat.m2; result.m9 = mat.m6; result.m10 = mat.m10; result.m11 = mat.m14; result.m12 = mat.m3; result.m13 = mat.m7; result.m14 = mat.m11; result.m15 = mat.m15; return result; } // Invert provided matrix RMAPI Matrix MatrixInvert(Matrix mat) { Matrix result = { 0 }; // Cache the matrix values (speed optimization) float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; float b00 = a00*a11 - a01*a10; float b01 = a00*a12 - a02*a10; float b02 = a00*a13 - a03*a10; float b03 = a01*a12 - a02*a11; float b04 = a01*a13 - a03*a11; float b05 = a02*a13 - a03*a12; float b06 = a20*a31 - a21*a30; float b07 = a20*a32 - a22*a30; float b08 = a20*a33 - a23*a30; float b09 = a21*a32 - a22*a31; float b10 = a21*a33 - a23*a31; float b11 = a22*a33 - a23*a32; // Calculate the invert determinant (inlined to avoid double-caching) float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; return result; } // Get identity matrix RMAPI Matrix MatrixIdentity(void) { Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; return result; } // Add two matrices RMAPI Matrix MatrixAdd(Matrix left, Matrix right) { Matrix result = { 0 }; result.m0 = left.m0 + right.m0; result.m1 = left.m1 + right.m1; result.m2 = left.m2 + right.m2; result.m3 = left.m3 + right.m3; result.m4 = left.m4 + right.m4; result.m5 = left.m5 + right.m5; result.m6 = left.m6 + right.m6; result.m7 = left.m7 + right.m7; result.m8 = left.m8 + right.m8; result.m9 = left.m9 + right.m9; result.m10 = left.m10 + right.m10; result.m11 = left.m11 + right.m11; result.m12 = left.m12 + right.m12; result.m13 = left.m13 + right.m13; result.m14 = left.m14 + right.m14; result.m15 = left.m15 + right.m15; return result; } // Subtract two matrices (left - right) RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) { Matrix result = { 0 }; result.m0 = left.m0 - right.m0; result.m1 = left.m1 - right.m1; result.m2 = left.m2 - right.m2; result.m3 = left.m3 - right.m3; result.m4 = left.m4 - right.m4; result.m5 = left.m5 - right.m5; result.m6 = left.m6 - right.m6; result.m7 = left.m7 - right.m7; result.m8 = left.m8 - right.m8; result.m9 = left.m9 - right.m9; result.m10 = left.m10 - right.m10; result.m11 = left.m11 - right.m11; result.m12 = left.m12 - right.m12; result.m13 = left.m13 - right.m13; result.m14 = left.m14 - right.m14; result.m15 = left.m15 - right.m15; return result; } // Get two matrix multiplication // NOTE: When multiplying matrices... the order matters! RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) { Matrix result = { 0 }; result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; return result; } // Get translation matrix RMAPI Matrix MatrixTranslate(float x, float y, float z) { Matrix result = { 1.0f, 0.0f, 0.0f, x, 0.0f, 1.0f, 0.0f, y, 0.0f, 0.0f, 1.0f, z, 0.0f, 0.0f, 0.0f, 1.0f }; return result; } // Create rotation matrix from axis and angle // NOTE: Angle should be provided in radians RMAPI Matrix MatrixRotate(Vector3 axis, float angle) { Matrix result = { 0 }; float x = axis.x, y = axis.y, z = axis.z; float lengthSquared = x*x + y*y + z*z; if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) { float ilength = 1.0f/sqrtf(lengthSquared); x *= ilength; y *= ilength; z *= ilength; } float sinres = sinf(angle); float cosres = cosf(angle); float t = 1.0f - cosres; result.m0 = x*x*t + cosres; result.m1 = y*x*t + z*sinres; result.m2 = z*x*t - y*sinres; result.m3 = 0.0f; result.m4 = x*y*t - z*sinres; result.m5 = y*y*t + cosres; result.m6 = z*y*t + x*sinres; result.m7 = 0.0f; result.m8 = x*z*t + y*sinres; result.m9 = y*z*t - x*sinres; result.m10 = z*z*t + cosres; result.m11 = 0.0f; result.m12 = 0.0f; result.m13 = 0.0f; result.m14 = 0.0f; result.m15 = 1.0f; return result; } // Get x-rotation matrix // NOTE: Angle must be provided in radians RMAPI Matrix MatrixRotateX(float angle) { Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() float cosres = cosf(angle); float sinres = sinf(angle); result.m5 = cosres; result.m6 = sinres; result.m9 = -sinres; result.m10 = cosres; return result; } // Get y-rotation matrix // NOTE: Angle must be provided in radians RMAPI Matrix MatrixRotateY(float angle) { Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() float cosres = cosf(angle); float sinres = sinf(angle); result.m0 = cosres; result.m2 = -sinres; result.m8 = sinres; result.m10 = cosres; return result; } // Get z-rotation matrix // NOTE: Angle must be provided in radians RMAPI Matrix MatrixRotateZ(float angle) { Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() float cosres = cosf(angle); float sinres = sinf(angle); result.m0 = cosres; result.m1 = sinres; result.m4 = -sinres; result.m5 = cosres; return result; } // Get xyz-rotation matrix // NOTE: Angle must be provided in radians RMAPI Matrix MatrixRotateXYZ(Vector3 angle) { Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() float cosz = cosf(-angle.z); float sinz = sinf(-angle.z); float cosy = cosf(-angle.y); float siny = sinf(-angle.y); float cosx = cosf(-angle.x); float sinx = sinf(-angle.x); result.m0 = cosz*cosy; result.m1 = (cosz*siny*sinx) - (sinz*cosx); result.m2 = (cosz*siny*cosx) + (sinz*sinx); result.m4 = sinz*cosy; result.m5 = (sinz*siny*sinx) + (cosz*cosx); result.m6 = (sinz*siny*cosx) - (cosz*sinx); result.m8 = -siny; result.m9 = cosy*sinx; result.m10= cosy*cosx; return result; } // Get zyx-rotation matrix // NOTE: Angle must be provided in radians RMAPI Matrix MatrixRotateZYX(Vector3 angle) { Matrix result = { 0 }; float cz = cosf(angle.z); float sz = sinf(angle.z); float cy = cosf(angle.y); float sy = sinf(angle.y); float cx = cosf(angle.x); float sx = sinf(angle.x); result.m0 = cz*cy; result.m4 = cz*sy*sx - cx*sz; result.m8 = sz*sx + cz*cx*sy; result.m12 = 0; result.m1 = cy*sz; result.m5 = cz*cx + sz*sy*sx; result.m9 = cx*sz*sy - cz*sx; result.m13 = 0; result.m2 = -sy; result.m6 = cy*sx; result.m10 = cy*cx; result.m14 = 0; result.m3 = 0; result.m7 = 0; result.m11 = 0; result.m15 = 1; return result; } // Get scaling matrix RMAPI Matrix MatrixScale(float x, float y, float z) { Matrix result = { x, 0.0f, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 0.0f, z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; return result; } // Get perspective projection matrix RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) { Matrix result = { 0 }; float rl = (float)(right - left); float tb = (float)(top - bottom); float fn = (float)(farPlane - nearPlane); result.m0 = ((float)nearPlane*2.0f)/rl; result.m1 = 0.0f; result.m2 = 0.0f; result.m3 = 0.0f; result.m4 = 0.0f; result.m5 = ((float)nearPlane*2.0f)/tb; result.m6 = 0.0f; result.m7 = 0.0f; result.m8 = ((float)right + (float)left)/rl; result.m9 = ((float)top + (float)bottom)/tb; result.m10 = -((float)farPlane + (float)nearPlane)/fn; result.m11 = -1.0f; result.m12 = 0.0f; result.m13 = 0.0f; result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; result.m15 = 0.0f; return result; } // Get perspective projection matrix // NOTE: Fovy angle must be provided in radians RMAPI Matrix MatrixPerspective(double fovY, double aspect, double nearPlane, double farPlane) { Matrix result = { 0 }; double top = nearPlane*tan(fovY*0.5); double bottom = -top; double right = top*aspect; double left = -right; // MatrixFrustum(-right, right, -top, top, near, far); float rl = (float)(right - left); float tb = (float)(top - bottom); float fn = (float)(farPlane - nearPlane); result.m0 = ((float)nearPlane*2.0f)/rl; result.m5 = ((float)nearPlane*2.0f)/tb; result.m8 = ((float)right + (float)left)/rl; result.m9 = ((float)top + (float)bottom)/tb; result.m10 = -((float)farPlane + (float)nearPlane)/fn; result.m11 = -1.0f; result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; return result; } // Get orthographic projection matrix RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane) { Matrix result = { 0 }; float rl = (float)(right - left); float tb = (float)(top - bottom); float fn = (float)(farPlane - nearPlane); result.m0 = 2.0f/rl; result.m1 = 0.0f; result.m2 = 0.0f; result.m3 = 0.0f; result.m4 = 0.0f; result.m5 = 2.0f/tb; result.m6 = 0.0f; result.m7 = 0.0f; result.m8 = 0.0f; result.m9 = 0.0f; result.m10 = -2.0f/fn; result.m11 = 0.0f; result.m12 = -((float)left + (float)right)/rl; result.m13 = -((float)top + (float)bottom)/tb; result.m14 = -((float)farPlane + (float)nearPlane)/fn; result.m15 = 1.0f; return result; } // Get camera look-at matrix (view matrix) RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) { Matrix result = { 0 }; float length = 0.0f; float ilength = 0.0f; // Vector3Subtract(eye, target) Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; // Vector3Normalize(vz) Vector3 v = vz; length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); if (length == 0.0f) length = 1.0f; ilength = 1.0f/length; vz.x *= ilength; vz.y *= ilength; vz.z *= ilength; // Vector3CrossProduct(up, vz) Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; // Vector3Normalize(x) v = vx; length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); if (length == 0.0f) length = 1.0f; ilength = 1.0f/length; vx.x *= ilength; vx.y *= ilength; vx.z *= ilength; // Vector3CrossProduct(vz, vx) Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; result.m0 = vx.x; result.m1 = vy.x; result.m2 = vz.x; result.m3 = 0.0f; result.m4 = vx.y; result.m5 = vy.y; result.m6 = vz.y; result.m7 = 0.0f; result.m8 = vx.z; result.m9 = vy.z; result.m10 = vz.z; result.m11 = 0.0f; result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) result.m15 = 1.0f; return result; } // Get float array of matrix data RMAPI float16 MatrixToFloatV(Matrix mat) { float16 result = { 0 }; result.v[0] = mat.m0; result.v[1] = mat.m1; result.v[2] = mat.m2; result.v[3] = mat.m3; result.v[4] = mat.m4; result.v[5] = mat.m5; result.v[6] = mat.m6; result.v[7] = mat.m7; result.v[8] = mat.m8; result.v[9] = mat.m9; result.v[10] = mat.m10; result.v[11] = mat.m11; result.v[12] = mat.m12; result.v[13] = mat.m13; result.v[14] = mat.m14; result.v[15] = mat.m15; return result; } //---------------------------------------------------------------------------------- // Module Functions Definition - Quaternion math //---------------------------------------------------------------------------------- // Add two quaternions RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) { Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; return result; } // Add quaternion and float value RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) { Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; return result; } // Subtract two quaternions RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) { Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; return result; } // Subtract quaternion and float value RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) { Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; return result; } // Get identity quaternion RMAPI Quaternion QuaternionIdentity(void) { Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; return result; } // Computes the length of a quaternion RMAPI float QuaternionLength(Quaternion q) { float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); return result; } // Normalize provided quaternion RMAPI Quaternion QuaternionNormalize(Quaternion q) { Quaternion result = { 0 }; float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); if (length == 0.0f) length = 1.0f; float ilength = 1.0f/length; result.x = q.x*ilength; result.y = q.y*ilength; result.z = q.z*ilength; result.w = q.w*ilength; return result; } // Invert provided quaternion RMAPI Quaternion QuaternionInvert(Quaternion q) { Quaternion result = q; float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; if (lengthSq != 0.0f) { float invLength = 1.0f/lengthSq; result.x *= -invLength; result.y *= -invLength; result.z *= -invLength; result.w *= invLength; } return result; } // Calculate two quaternion multiplication RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) { Quaternion result = { 0 }; float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; return result; } // Scale quaternion by float value RMAPI Quaternion QuaternionScale(Quaternion q, float mul) { Quaternion result = { 0 }; result.x = q.x*mul; result.y = q.y*mul; result.z = q.z*mul; result.w = q.w*mul; return result; } // Divide two quaternions RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) { Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; return result; } // Calculate linear interpolation between two quaternions RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) { Quaternion result = { 0 }; result.x = q1.x + amount*(q2.x - q1.x); result.y = q1.y + amount*(q2.y - q1.y); result.z = q1.z + amount*(q2.z - q1.z); result.w = q1.w + amount*(q2.w - q1.w); return result; } // Calculate slerp-optimized interpolation between two quaternions RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) { Quaternion result = { 0 }; // QuaternionLerp(q1, q2, amount) result.x = q1.x + amount*(q2.x - q1.x); result.y = q1.y + amount*(q2.y - q1.y); result.z = q1.z + amount*(q2.z - q1.z); result.w = q1.w + amount*(q2.w - q1.w); // QuaternionNormalize(q); Quaternion q = result; float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); if (length == 0.0f) length = 1.0f; float ilength = 1.0f/length; result.x = q.x*ilength; result.y = q.y*ilength; result.z = q.z*ilength; result.w = q.w*ilength; return result; } // Calculates spherical linear interpolation between two quaternions RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) { Quaternion result = { 0 }; #if !defined(EPSILON) #define EPSILON 0.000001f #endif float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; if (cosHalfTheta < 0) { q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; cosHalfTheta = -cosHalfTheta; } if (fabsf(cosHalfTheta) >= 1.0f) result = q1; else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); else { float halfTheta = acosf(cosHalfTheta); float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); if (fabsf(sinHalfTheta) < EPSILON) { result.x = (q1.x*0.5f + q2.x*0.5f); result.y = (q1.y*0.5f + q2.y*0.5f); result.z = (q1.z*0.5f + q2.z*0.5f); result.w = (q1.w*0.5f + q2.w*0.5f); } else { float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; float ratioB = sinf(amount*halfTheta)/sinHalfTheta; result.x = (q1.x*ratioA + q2.x*ratioB); result.y = (q1.y*ratioA + q2.y*ratioB); result.z = (q1.z*ratioA + q2.z*ratioB); result.w = (q1.w*ratioA + q2.w*ratioB); } } return result; } // Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm // as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t) { float t2 = t*t; float t3 = t2*t; float h00 = 2*t3 - 3*t2 + 1; float h10 = t3 - 2*t2 + t; float h01 = -2*t3 + 3*t2; float h11 = t3 - t2; Quaternion p0 = QuaternionScale(q1, h00); Quaternion m0 = QuaternionScale(outTangent1, h10); Quaternion p1 = QuaternionScale(q2, h01); Quaternion m1 = QuaternionScale(inTangent2, h11); Quaternion result = { 0 }; result = QuaternionAdd(p0, m0); result = QuaternionAdd(result, p1); result = QuaternionAdd(result, m1); result = QuaternionNormalize(result); return result; } // Calculate quaternion based on the rotation from one vector to another RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) { Quaternion result = { 0 }; float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) result.x = cross.x; result.y = cross.y; result.z = cross.z; result.w = 1.0f + cos2Theta; // QuaternionNormalize(q); // NOTE: Normalize to essentially nlerp the original and identity to 0.5 Quaternion q = result; float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); if (length == 0.0f) length = 1.0f; float ilength = 1.0f/length; result.x = q.x*ilength; result.y = q.y*ilength; result.z = q.z*ilength; result.w = q.w*ilength; return result; } // Get a quaternion for a given rotation matrix RMAPI Quaternion QuaternionFromMatrix(Matrix mat) { Quaternion result = { 0 }; float fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10; float fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10; float fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10; float fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5; int biggestIndex = 0; float fourBiggestSquaredMinus1 = fourWSquaredMinus1; if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { fourBiggestSquaredMinus1 = fourXSquaredMinus1; biggestIndex = 1; } if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { fourBiggestSquaredMinus1 = fourYSquaredMinus1; biggestIndex = 2; } if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { fourBiggestSquaredMinus1 = fourZSquaredMinus1; biggestIndex = 3; } float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f)*0.5f; float mult = 0.25f/biggestVal; switch (biggestIndex) { case 0: result.w = biggestVal; result.x = (mat.m6 - mat.m9)*mult; result.y = (mat.m8 - mat.m2)*mult; result.z = (mat.m1 - mat.m4)*mult; break; case 1: result.x = biggestVal; result.w = (mat.m6 - mat.m9)*mult; result.y = (mat.m1 + mat.m4)*mult; result.z = (mat.m8 + mat.m2)*mult; break; case 2: result.y = biggestVal; result.w = (mat.m8 - mat.m2)*mult; result.x = (mat.m1 + mat.m4)*mult; result.z = (mat.m6 + mat.m9)*mult; break; case 3: result.z = biggestVal; result.w = (mat.m1 - mat.m4)*mult; result.x = (mat.m8 + mat.m2)*mult; result.y = (mat.m6 + mat.m9)*mult; break; } return result; } // Get a matrix for a given quaternion RMAPI Matrix QuaternionToMatrix(Quaternion q) { Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() float a2 = q.x*q.x; float b2 = q.y*q.y; float c2 = q.z*q.z; float ac = q.x*q.z; float ab = q.x*q.y; float bc = q.y*q.z; float ad = q.w*q.x; float bd = q.w*q.y; float cd = q.w*q.z; result.m0 = 1 - 2*(b2 + c2); result.m1 = 2*(ab + cd); result.m2 = 2*(ac - bd); result.m4 = 2*(ab - cd); result.m5 = 1 - 2*(a2 + c2); result.m6 = 2*(bc + ad); result.m8 = 2*(ac + bd); result.m9 = 2*(bc - ad); result.m10 = 1 - 2*(a2 + b2); return result; } // Get rotation quaternion for an angle and axis // NOTE: Angle must be provided in radians RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) { Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; float axisLength = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); if (axisLength != 0.0f) { angle *= 0.5f; float length = 0.0f; float ilength = 0.0f; // Vector3Normalize(axis) length = axisLength; if (length == 0.0f) length = 1.0f; ilength = 1.0f/length; axis.x *= ilength; axis.y *= ilength; axis.z *= ilength; float sinres = sinf(angle); float cosres = cosf(angle); result.x = axis.x*sinres; result.y = axis.y*sinres; result.z = axis.z*sinres; result.w = cosres; // QuaternionNormalize(q); Quaternion q = result; length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); if (length == 0.0f) length = 1.0f; ilength = 1.0f/length; result.x = q.x*ilength; result.y = q.y*ilength; result.z = q.z*ilength; result.w = q.w*ilength; } return result; } // Get the rotation angle and axis for a given quaternion RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) { if (fabsf(q.w) > 1.0f) { // QuaternionNormalize(q); float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); if (length == 0.0f) length = 1.0f; float ilength = 1.0f/length; q.x = q.x*ilength; q.y = q.y*ilength; q.z = q.z*ilength; q.w = q.w*ilength; } Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; float resAngle = 2.0f*acosf(q.w); float den = sqrtf(1.0f - q.w*q.w); if (den > EPSILON) { resAxis.x = q.x/den; resAxis.y = q.y/den; resAxis.z = q.z/den; } else { // This occurs when the angle is zero. // Not a problem: just set an arbitrary normalized axis. resAxis.x = 1.0f; } *outAxis = resAxis; *outAngle = resAngle; } // Get the quaternion equivalent to Euler angles // NOTE: Rotation order is ZYX RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) { Quaternion result = { 0 }; float x0 = cosf(pitch*0.5f); float x1 = sinf(pitch*0.5f); float y0 = cosf(yaw*0.5f); float y1 = sinf(yaw*0.5f); float z0 = cosf(roll*0.5f); float z1 = sinf(roll*0.5f); result.x = x1*y0*z0 - x0*y1*z1; result.y = x0*y1*z0 + x1*y0*z1; result.z = x0*y0*z1 - x1*y1*z0; result.w = x0*y0*z0 + x1*y1*z1; return result; } // Get the Euler angles equivalent to quaternion (roll, pitch, yaw) // NOTE: Angles are returned in a Vector3 struct in radians RMAPI Vector3 QuaternionToEuler(Quaternion q) { Vector3 result = { 0 }; // Roll (x-axis rotation) float x0 = 2.0f*(q.w*q.x + q.y*q.z); float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); result.x = atan2f(x0, x1); // Pitch (y-axis rotation) float y0 = 2.0f*(q.w*q.y - q.z*q.x); y0 = y0 > 1.0f ? 1.0f : y0; y0 = y0 < -1.0f ? -1.0f : y0; result.y = asinf(y0); // Yaw (z-axis rotation) float z0 = 2.0f*(q.w*q.z + q.x*q.y); float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); result.z = atan2f(z0, z1); return result; } // Transform a quaternion given a transformation matrix RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) { Quaternion result = { 0 }; result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; return result; } // Check whether two given quaternions are almost equal RMAPI int QuaternionEquals(Quaternion p, Quaternion q) { #if !defined(EPSILON) #define EPSILON 0.000001f #endif int result = (((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))) || (((fabsf(p.x + q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && ((fabsf(p.y + q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && ((fabsf(p.z + q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && ((fabsf(p.w + q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))); return result; } // Decompose a transformation matrix into its rotational, translational and scaling components RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) { // Extract translation. translation->x = mat.m12; translation->y = mat.m13; translation->z = mat.m14; // Extract upper-left for determinant computation const float a = mat.m0; const float b = mat.m4; const float c = mat.m8; const float d = mat.m1; const float e = mat.m5; const float f = mat.m9; const float g = mat.m2; const float h = mat.m6; const float i = mat.m10; const float A = e*i - f*h; const float B = f*g - d*i; const float C = d*h - e*g; // Extract scale const float det = a*A + b*B + c*C; Vector3 abc = { a, b, c }; Vector3 def = { d, e, f }; Vector3 ghi = { g, h, i }; float scalex = Vector3Length(abc); float scaley = Vector3Length(def); float scalez = Vector3Length(ghi); Vector3 s = { scalex, scaley, scalez }; if (det < 0) s = Vector3Negate(s); *scale = s; // Remove scale from the matrix if it is not close to zero Matrix clone = mat; if (!FloatEquals(det, 0)) { clone.m0 /= s.x; clone.m4 /= s.x; clone.m8 /= s.x; clone.m1 /= s.y; clone.m5 /= s.y; clone.m9 /= s.y; clone.m2 /= s.z; clone.m6 /= s.z; clone.m10 /= s.z; // Extract rotation *rotation = QuaternionFromMatrix(clone); } else { // Set to identity if close to zero *rotation = QuaternionIdentity(); } } #if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) // Optional C++ math operators //------------------------------------------------------------------------------- // Vector2 operators static constexpr Vector2 Vector2Zeros = { 0, 0 }; static constexpr Vector2 Vector2Ones = { 1, 1 }; static constexpr Vector2 Vector2UnitX = { 1, 0 }; static constexpr Vector2 Vector2UnitY = { 0, 1 }; inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) { return Vector2Add(lhs, rhs); } inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) { lhs = Vector2Add(lhs, rhs); return lhs; } inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) { return Vector2Subtract(lhs, rhs); } inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) { lhs = Vector2Subtract(lhs, rhs); return lhs; } inline Vector2 operator * (const Vector2& lhs, const float& rhs) { return Vector2Scale(lhs, rhs); } inline const Vector2& operator *= (Vector2& lhs, const float& rhs) { lhs = Vector2Scale(lhs, rhs); return lhs; } inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) { return Vector2Multiply(lhs, rhs); } inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) { lhs = Vector2Multiply(lhs, rhs); return lhs; } inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) { return Vector2Transform(lhs, rhs); } inline const Vector2& operator -= (Vector2& lhs, const Matrix& rhs) { lhs = Vector2Transform(lhs, rhs); return lhs; } inline Vector2 operator / (const Vector2& lhs, const float& rhs) { return Vector2Scale(lhs, 1.0f / rhs); } inline const Vector2& operator /= (Vector2& lhs, const float& rhs) { lhs = Vector2Scale(lhs, rhs); return lhs; } inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) { return Vector2Divide(lhs, rhs); } inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) { lhs = Vector2Divide(lhs, rhs); return lhs; } inline bool operator == (const Vector2& lhs, const Vector2& rhs) { return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); } inline bool operator != (const Vector2& lhs, const Vector2& rhs) { return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); } // Vector3 operators static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) { return Vector3Add(lhs, rhs); } inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) { lhs = Vector3Add(lhs, rhs); return lhs; } inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) { return Vector3Subtract(lhs, rhs); } inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) { lhs = Vector3Subtract(lhs, rhs); return lhs; } inline Vector3 operator * (const Vector3& lhs, const float& rhs) { return Vector3Scale(lhs, rhs); } inline const Vector3& operator *= (Vector3& lhs, const float& rhs) { lhs = Vector3Scale(lhs, rhs); return lhs; } inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) { return Vector3Multiply(lhs, rhs); } inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) { lhs = Vector3Multiply(lhs, rhs); return lhs; } inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) { return Vector3Transform(lhs, rhs); } inline const Vector3& operator -= (Vector3& lhs, const Matrix& rhs) { lhs = Vector3Transform(lhs, rhs); return lhs; } inline Vector3 operator / (const Vector3& lhs, const float& rhs) { return Vector3Scale(lhs, 1.0f / rhs); } inline const Vector3& operator /= (Vector3& lhs, const float& rhs) { lhs = Vector3Scale(lhs, rhs); return lhs; } inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) { return Vector3Divide(lhs, rhs); } inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) { lhs = Vector3Divide(lhs, rhs); return lhs; } inline bool operator == (const Vector3& lhs, const Vector3& rhs) { return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); } inline bool operator != (const Vector3& lhs, const Vector3& rhs) { return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); } // Vector4 operators static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) { return Vector4Add(lhs, rhs); } inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) { lhs = Vector4Add(lhs, rhs); return lhs; } inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) { return Vector4Subtract(lhs, rhs); } inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) { lhs = Vector4Subtract(lhs, rhs); return lhs; } inline Vector4 operator * (const Vector4& lhs, const float& rhs) { return Vector4Scale(lhs, rhs); } inline const Vector4& operator *= (Vector4& lhs, const float& rhs) { lhs = Vector4Scale(lhs, rhs); return lhs; } inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) { return Vector4Multiply(lhs, rhs); } inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) { lhs = Vector4Multiply(lhs, rhs); return lhs; } inline Vector4 operator / (const Vector4& lhs, const float& rhs) { return Vector4Scale(lhs, 1.0f / rhs); } inline const Vector4& operator /= (Vector4& lhs, const float& rhs) { lhs = Vector4Scale(lhs, rhs); return lhs; } inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) { return Vector4Divide(lhs, rhs); } inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) { lhs = Vector4Divide(lhs, rhs); return lhs; } inline bool operator == (const Vector4& lhs, const Vector4& rhs) { return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); } inline bool operator != (const Vector4& lhs, const Vector4& rhs) { return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); } // Quaternion operators static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; inline Quaternion operator + (const Quaternion& lhs, const float& rhs) { return QuaternionAddValue(lhs, rhs); } inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) { lhs = QuaternionAddValue(lhs, rhs); return lhs; } inline Quaternion operator - (const Quaternion& lhs, const float& rhs) { return QuaternionSubtractValue(lhs, rhs); } inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) { lhs = QuaternionSubtractValue(lhs, rhs); return lhs; } inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) { return QuaternionTransform(lhs, rhs); } inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) { lhs = QuaternionTransform(lhs, rhs); return lhs; } // Matrix operators inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) { return MatrixAdd(lhs, rhs); } inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) { lhs = MatrixAdd(lhs, rhs); return lhs; } inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) { return MatrixSubtract(lhs, rhs); } inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) { lhs = MatrixSubtract(lhs, rhs); return lhs; } inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) { return MatrixMultiply(lhs, rhs); } inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) { lhs = MatrixMultiply(lhs, rhs); return lhs; } //------------------------------------------------------------------------------- #endif // C++ operators #endif // RAYMATH_H ================================================ FILE: renderers/sokol/sokol_clay.h ================================================ #ifndef SOKOL_CLAY_INCLUDED #define SOKOL_CLAY_INCLUDED (1) /* sokol_clay.h -- drop-in Clay renderer for sokol_gfx.h Do this: #define SOKOL_CLAY_IMPL before you include this file in *one* C file to create the implementation. Optionally provide the following configuration define both before including the the declaration and implementation: SOKOL_CLAY_NO_SOKOL_APP - don't depend on sokol_app.h (see below for details) Include the following headers before sokol_clay.h (both before including the declaration and implementation): sokol_gl.h sokol_fontstash.h sokol_app.h (except SOKOL_CLAY_NO_SOKOL_APP) clay.h FEATURE OVERVIEW: ================= sokol_clay.h implements the rendering and event-handling code for Clay (https://github.com/nicbarker/clay) on top of sokol_gl.h and (optionally) sokol_app.h. Since sokol_fontstash.h already depends on sokol_gl.h, the rendering is implemented using sokol_gl calls. (TODO: make fontstash optional?) The sokol_app.h dependency is optional and used for input event handling. If you only use sokol_gfx.h but not sokol_app.h in your application, define SOKOL_CLAY_NO_SOKOL_APP before including the implementation of sokol_clay.h, this will remove any dependency to sokol_app.h, but you must call sclay_set_layout_dimensions and handle input yourself. sokol_clay.h is not thread-safe, all calls must be made from the same thread where sokol_gfx.h is running. HOWTO: ====== --- To initialize sokol-clay, call sclay_setup(). This can be done before or after Clay_Initialize. --- Create an array of sclay_font_t and fill it by calling one of: sclay_font_t sclay_add_font(const char *filename); sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen); The fontId value in Clay corresponds to indices in this array. After calling Clay_Initialize but before calling any layout code, do this: Clay_SetMeasureTextFunction(sclay_measure_text, &fonts); where `fonts` is the abovementioned array. --- At the start of a frame, call sclay_new_frame() if you're using sokol_app.h. If you're not using sokol_app.h, call: void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale); at the start of the frame (or just when the window is resized.) Either way, do some layout, then at the end of the frame call sclay_render: sg_begin_pass(...) // other rendering... sclay_render(renderCommands, &fonts); // other rendering... sgl_draw(); sg_end_pass(); sg_commit(); One caveat: sclay_render assumes the default gl view matrix, and handles scaling automatically. If you've adjusted the view matrix, remember to first call: sgl_matrix_mode_modelview(); sgl_load_identity(); before calling sclay_render. --- if you're using sokol_app.h, from inside the sokol_app.h event callback, call: void sclay_handle_event(const sapp_event* ev); Unfortunately Clay does not currently provide feedback on whether a mouse click was handled or not. --- if you want to use images with clay, you should pass a pointer to a sclay_image to the CLAY macro, like this: CLAY({ ... .image = { .imageData = &(sclay_image){ .view = view, .sampler = 0 } }, }) Using 0 as a sampler uses the sokol default sampler with linear interpolation. The image should be created using sg_make_image from sokol_gfx. --- finally, on application shutdown, call sclay_shutdown() */ #if !defined(SOKOL_CLAY_NO_SOKOL_APP) && !defined(SOKOL_APP_INCLUDED) #error "Please include sokol_app.h before sokol_clay.h (or define SOKOL_CLAY_NO_SOKOL_APP)" #endif typedef int sclay_font_t; typedef struct sclay_image { sg_view view; sg_sampler sampler; struct { float u0, v0, u1, v1; } uv; } sclay_image; void sclay_setup(); void sclay_shutdown(); sclay_font_t sclay_add_font(const char *filename); sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen); Clay_Dimensions sclay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); #ifndef SOKOL_CLAY_NO_SOKOL_APP void sclay_new_frame(); void sclay_handle_event(const sapp_event *ev); #endif /* SOKOL_CLAY_NO_SOKOL_APP */ /* Use this if you don't call sclay_new_frame. `size` is the "virtual" size which * your layout is relative to (ie. the actual framebuffer size divided by dpi_scale.) * Set dpi_scale to 1 if you're not using high-dpi support. */ void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale); void sclay_render(Clay_RenderCommandArray renderCommands, sclay_font_t *fonts); #endif /* SOKOL_CLAY_INCLUDED */ #ifdef SOKOL_CLAY_IMPL #define SOKOL_CLAY_IMPL_INCLUDED (1) #ifndef SOKOL_GL_INCLUDED #error "Please include sokol_gl.h before sokol_clay.h" #endif #ifndef SOKOL_FONTSTASH_INCLUDED #error "Please include sokol_fontstash.h before sokol_clay.h" #endif #ifndef CLAY_HEADER #error "Please include clay.h before sokol_clay.h" #endif typedef struct { sgl_pipeline pip; #ifndef SOKOL_CLAY_NO_SOKOL_APP Clay_Vector2 mouse_pos, scroll; bool mouse_down; #endif Clay_Dimensions size; float dpi_scale; FONScontext *fonts; } _sclay_state_t; static _sclay_state_t _sclay; void sclay_setup() { _sclay.pip = sgl_make_pipeline(&(sg_pipeline_desc){ .colors[0] = { .blend = { .enabled = true, .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, }, } }); #ifndef SOKOL_CLAY_NO_SOKOL_APP _sclay.mouse_pos = (Clay_Vector2){0, 0}; _sclay.scroll = (Clay_Vector2){0, 0}; _sclay.mouse_down = false; #endif _sclay.size = (Clay_Dimensions){1, 1}; _sclay.dpi_scale = 1; _sclay.fonts = sfons_create(&(sfons_desc_t){ 0 }); //TODO clay error handler? } void sclay_shutdown() { sgl_destroy_pipeline(_sclay.pip); sfons_destroy(_sclay.fonts); } #ifndef SOKOL_CLAY_NO_SOKOL_APP void sclay_handle_event(const sapp_event* ev) { switch(ev->type){ case SAPP_EVENTTYPE_MOUSE_MOVE: _sclay.mouse_pos.x = ev->mouse_x / _sclay.dpi_scale; _sclay.mouse_pos.y = ev->mouse_y / _sclay.dpi_scale; break; case SAPP_EVENTTYPE_MOUSE_DOWN: _sclay.mouse_down = true; break; case SAPP_EVENTTYPE_MOUSE_UP: _sclay.mouse_down = false; break; case SAPP_EVENTTYPE_MOUSE_SCROLL: _sclay.scroll.x += ev->scroll_x; _sclay.scroll.y += ev->scroll_y; break; default: break; } } void sclay_new_frame() { sclay_set_layout_dimensions((Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, sapp_dpi_scale()); Clay_SetPointerState(_sclay.mouse_pos, _sclay.mouse_down); Clay_UpdateScrollContainers(true, _sclay.scroll, sapp_frame_duration()); _sclay.scroll = (Clay_Vector2){0, 0}; } #endif /* SOKOL_CLAY_NO_SOKOL_APP */ void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale) { size.width /= dpi_scale; size.height /= dpi_scale; _sclay.size = size; if(_sclay.dpi_scale != dpi_scale){ _sclay.dpi_scale = dpi_scale; Clay_ResetMeasureTextCache(); } Clay_SetLayoutDimensions(size); } sclay_font_t sclay_add_font(const char *filename) { //TODO log something if we get FONS_INVALID return fonsAddFont(_sclay.fonts, "", filename); } sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen) { //TODO log something if we get FONS_INVALID return fonsAddFontMem(_sclay.fonts, "", data, dataLen, false); } Clay_Dimensions sclay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { sclay_font_t *fonts = (sclay_font_t *)userData; if(!fonts) return (Clay_Dimensions){ 0 }; fonsSetFont(_sclay.fonts, fonts[config->fontId]); fonsSetSize(_sclay.fonts, config->fontSize * _sclay.dpi_scale); fonsSetSpacing(_sclay.fonts, config->letterSpacing * _sclay.dpi_scale); fonsSetAlign(_sclay.fonts, FONS_ALIGN_LEFT | FONS_ALIGN_TOP); float ascent, descent, lineh; fonsVertMetrics(_sclay.fonts, &ascent, &descent, &lineh); return (Clay_Dimensions) { .width = fonsTextBounds(_sclay.fonts, 0, 0, text.chars, text.chars + text.length, NULL) / _sclay.dpi_scale, .height = (ascent - descent) / _sclay.dpi_scale }; } static void _draw_rect(float x, float y, float w, float h){ sgl_v2f(x, y); sgl_v2f(x, y); sgl_v2f(x+w, y); sgl_v2f(x, y+h); sgl_v2f(x+w, y+h); sgl_v2f(x+w, y+h); } static void _draw_rect_textured(float x, float y, float w, float h, float u0, float v0, float u1, float v1){ sgl_v2f_t2f(x, y, u0, v0); sgl_v2f_t2f(x, y, u0, v0); sgl_v2f_t2f(x+w, y, u1, v0); sgl_v2f_t2f(x, y+h, u0, v1); sgl_v2f_t2f(x+w, y+h, u1, v1); sgl_v2f_t2f(x+w, y+h, u1, v1); } static float _SIN[16] = { 0.000000f, 0.104528f, 0.207912f, 0.309017f, 0.406737f, 0.500000f, 0.587785f, 0.669131f, 0.743145f, 0.809017f, 0.866025f, 0.913545f, 0.951057f, 0.978148f, 0.994522f, 1.000000f, }; /* rx,ry = radius */ static void _draw_corner(float x, float y, float rx, float ry){ x -= rx; y -= ry; sgl_v2f(x, y); for(int i = 0; i < 16; ++i){ sgl_v2f(x, y); sgl_v2f(x+(rx*_SIN[15-i]), y+(ry*_SIN[i])); } sgl_v2f(x+(rx*_SIN[0]), y+(ry*_SIN[15])); } static void _draw_corner_textured(float x, float y, float rx, float ry, float bx, float by, float bw, float bh, float u0, float v0, float u1, float v1) { x -= rx; y -= ry; #define MAP_U(x) (u0+(((x)-bx)/bw)*(u1-u0)) #define MAP_V(y) (v0+(((y)-by)/bh)*(v1-v0)) sgl_v2f_t2f(x, y, MAP_U(x), MAP_V(y)); for(int i = 0; i < 16; ++i){ sgl_v2f_t2f(x, y, MAP_U(x), MAP_V(y)); float px = x+(rx*_SIN[15-i]); float py = y+(ry*_SIN[i]); sgl_v2f_t2f(px, py, MAP_U(px), MAP_V(py)); } sgl_v2f_t2f(x+(rx*_SIN[0]), y+(ry*_SIN[15]), MAP_U(x+(rx*_SIN[0])), MAP_V(y+(ry*_SIN[15]))); #undef MAP_U #undef MAP_V } /* rx,ry = radius ix,iy = inner radius */ static void _draw_corner_border(float x, float y, float rx, float ry, float ix, float iy){ x -= rx; y -= ry; sgl_v2f(x+(ix*_SIN[15]), y+(iy*_SIN[0])); for(int i = 0; i < 16; ++i){ sgl_v2f(x+(ix*_SIN[15-i]), y+(iy*_SIN[i])); sgl_v2f(x+(rx*_SIN[15-i]), y+(ry*_SIN[i])); } sgl_v2f(x+(rx*_SIN[0]), y+(ry*_SIN[15])); } void sclay_render(Clay_RenderCommandArray renderCommands, sclay_font_t *fonts) { sgl_matrix_mode_modelview(); sgl_translate(-1.0f, 1.0f, 0.0f); sgl_scale(2.0f/_sclay.size.width, -2.0f/_sclay.size.height, 1.0f); sgl_disable_texture(); sgl_push_pipeline(); sgl_load_pipeline(_sclay.pip); for (uint32_t i = 0; i < renderCommands.length; i++) { Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i); Clay_BoundingBox bbox = renderCommand->boundingBox; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; sgl_c4f(config->backgroundColor.r / 255.0f, config->backgroundColor.g / 255.0f, config->backgroundColor.b / 255.0f, config->backgroundColor.a / 255.0f); Clay_CornerRadius r = config->cornerRadius; sgl_begin_triangle_strip(); if(r.topLeft > 0 || r.topRight > 0){ _draw_corner(bbox.x, bbox.y, -r.topLeft, -r.topLeft); _draw_corner(bbox.x+bbox.width, bbox.y, r.topRight, -r.topRight); _draw_rect(bbox.x+r.topLeft, bbox.y, bbox.width-r.topLeft-r.topRight, CLAY__MAX(r.topLeft, r.topRight)); } if(r.bottomLeft > 0 || r.bottomRight > 0){ _draw_corner(bbox.x, bbox.y+bbox.height, -r.bottomLeft, r.bottomLeft); _draw_corner(bbox.x+bbox.width, bbox.y+bbox.height, r.bottomRight, r.bottomRight); _draw_rect(bbox.x+r.bottomLeft, bbox.y+bbox.height-CLAY__MAX(r.bottomLeft, r.bottomRight), bbox.width-r.bottomLeft-r.bottomRight, CLAY__MAX(r.bottomLeft, r.bottomRight)); } if(r.topLeft < r.bottomLeft){ if(r.topLeft < r.topRight){ _draw_rect(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft); _draw_rect(bbox.x+r.topLeft, bbox.y+r.topRight, r.bottomLeft-r.topLeft, bbox.height-r.topRight-r.bottomLeft); } else { _draw_rect(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft); } } else { if(r.bottomLeft < r.bottomRight){ _draw_rect(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft); _draw_rect(bbox.x+r.bottomLeft, bbox.y+r.topLeft, r.topLeft-r.bottomLeft, bbox.height-r.topLeft-r.bottomRight); } else { _draw_rect(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft); } } if(r.topRight < r.bottomRight){ if(r.topRight < r.topLeft){ _draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topLeft, r.bottomRight-r.topRight, bbox.height-r.topLeft-r.bottomRight); _draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, r.topRight, bbox.height-r.topRight-r.bottomRight); } else { _draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight, r.bottomRight, bbox.height-r.topRight-r.bottomRight); } } else { if(r.bottomRight < r.bottomLeft){ _draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, r.topRight-r.bottomRight, bbox.height-r.topRight-r.bottomLeft); _draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight, r.bottomRight, bbox.height-r.topRight-r.bottomRight); } else { _draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, r.topRight, bbox.height-r.topRight-r.bottomRight); } } _draw_rect(bbox.x+CLAY__MAX(r.topLeft, r.bottomLeft), bbox.y+CLAY__MAX(r.topLeft, r.topRight), bbox.width-CLAY__MAX(r.topLeft, r.bottomLeft)-CLAY__MAX(r.topRight, r.bottomRight), bbox.height-CLAY__MAX(r.topLeft, r.topRight)-CLAY__MAX(r.bottomLeft, r.bottomRight)); sgl_end(); break; } case CLAY_RENDER_COMMAND_TYPE_TEXT: { if(!fonts) break; Clay_TextRenderData *config = &renderCommand->renderData.text; Clay_StringSlice text = config->stringContents; fonsSetFont(_sclay.fonts, fonts[config->fontId]); uint32_t color = sfons_rgba( config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a); fonsSetColor(_sclay.fonts, color); fonsSetSpacing(_sclay.fonts, config->letterSpacing * _sclay.dpi_scale); fonsSetAlign(_sclay.fonts, FONS_ALIGN_LEFT | FONS_ALIGN_TOP); fonsSetSize(_sclay.fonts, config->fontSize * _sclay.dpi_scale); sgl_matrix_mode_modelview(); sgl_push_matrix(); sgl_scale(1.0f/_sclay.dpi_scale, 1.0f/_sclay.dpi_scale, 1.0f); fonsDrawText(_sclay.fonts, bbox.x*_sclay.dpi_scale, bbox.y*_sclay.dpi_scale, text.chars, text.chars + text.length); sgl_pop_matrix(); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { sgl_scissor_rectf(bbox.x*_sclay.dpi_scale, bbox.y*_sclay.dpi_scale, bbox.width*_sclay.dpi_scale, bbox.height*_sclay.dpi_scale, true); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { sgl_scissor_rectf(0, 0, _sclay.size.width*_sclay.dpi_scale, _sclay.size.height*_sclay.dpi_scale, true); break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { Clay_ImageRenderData *config = &renderCommand->renderData.image; sclay_image* img = (sclay_image*)config->imageData; // by default, u1 and v1 are 1. if we pass 0. // note, we are modifying a copy ! float u0 = img->uv.u0; float v0 = img->uv.v0; float u1 = img->uv.u1; float v1 = img->uv.v1; if (u1 == 0.f) { u1 = 1.f; } if (v1 == 0.f) { v1 = 1.f; } int untinted = config->backgroundColor.r == 0 && config->backgroundColor.g == 0 && config->backgroundColor.b == 0 && config->backgroundColor.a == 0; float cr = untinted ? 1.f : (config->backgroundColor.r / 255.0f); float gr = untinted ? 1.f : (config->backgroundColor.g / 255.0f); float br = untinted ? 1.f : (config->backgroundColor.b / 255.0f); float ar = untinted ? 1.f : (config->backgroundColor.a / 255.0f); sgl_c4f(cr, gr, br, ar); Clay_CornerRadius r = config->cornerRadius; sgl_enable_texture(); sgl_texture(img->view, img->sampler); sgl_begin_triangle_strip(); if(r.topLeft > 0 || r.topRight > 0){ _draw_corner_textured(bbox.x, bbox.y, -r.topLeft, -r.topLeft, bbox.x, bbox.y, bbox.width, bbox.height, u0, v0, u1, v1); _draw_corner_textured(bbox.x+bbox.width, bbox.y, r.topRight, -r.topRight, bbox.x, bbox.y, bbox.width, bbox.height, u0, v0, u1, v1); _draw_rect_textured(bbox.x+r.topLeft, bbox.y, bbox.width-r.topLeft-r.topRight, CLAY__MAX(r.topLeft, r.topRight), u0 + (r.topLeft/bbox.width)*(u1-u0), v0, u1 - (r.topRight/bbox.width)*(u1-u0), v0 + (CLAY__MAX(r.topLeft, r.topRight)/bbox.height)*(v1-v0)); } if(r.bottomLeft > 0 || r.bottomRight > 0){ _draw_corner_textured(bbox.x, bbox.y+bbox.height, -r.bottomLeft, r.bottomLeft, bbox.x, bbox.y, bbox.width, bbox.height, u0, v0, u1, v1); _draw_corner_textured(bbox.x+bbox.width, bbox.y+bbox.height, r.bottomRight, r.bottomRight, bbox.x, bbox.y, bbox.width, bbox.height, u0, v0, u1, v1); _draw_rect_textured(bbox.x+r.bottomLeft, bbox.y+bbox.height-CLAY__MAX(r.bottomLeft, r.bottomRight), bbox.width-r.bottomLeft-r.bottomRight, CLAY__MAX(r.bottomLeft, r.bottomRight), u0 + (r.bottomLeft/bbox.width)*(u1-u0), v1 - (CLAY__MAX(r.bottomLeft, r.bottomRight)/bbox.height)*(v1-v0), u1 - (r.bottomRight/bbox.width)*(u1-u0), v1); } if(r.topLeft < r.bottomLeft){ if(r.topLeft < r.topRight){ _draw_rect_textured(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft, u0, v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.topLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); _draw_rect_textured(bbox.x+r.topLeft, bbox.y+r.topRight, r.bottomLeft-r.topLeft, bbox.height-r.topRight-r.bottomLeft, u0 + (r.topLeft/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u0 + (r.topLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); } else { _draw_rect_textured(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft, u0, v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.bottomLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); } } else { if(r.bottomLeft < r.bottomRight){ _draw_rect_textured(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft, u0, v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.bottomLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); _draw_rect_textured(bbox.x+r.bottomLeft, bbox.y+r.topLeft, r.topLeft-r.bottomLeft, bbox.height-r.topLeft-r.bottomRight, u0 + (r.bottomLeft/bbox.width)*(u1-u0), v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.topLeft/bbox.width)*(u1-u0), v1 - (r.bottomRight/bbox.height)*(v1-v0)); } else { _draw_rect_textured(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft, u0, v0 + (r.topLeft/bbox.height)*(v1-v0), u0 + (r.topLeft/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); } } if(r.topRight < r.bottomRight){ if(r.topRight < r.topLeft){ _draw_rect_textured(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topLeft, r.bottomRight-r.topRight, bbox.height-r.topLeft-r.bottomRight, u1 - (r.bottomRight/bbox.width)*(u1-u0), v0 + (r.topLeft/bbox.height)*(v1-v0), u1 - (r.topRight/bbox.width)*(u1-u0), v1 - (r.bottomRight/bbox.height)*(v1-v0)); _draw_rect_textured(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, r.topRight, bbox.height-r.topRight-r.bottomRight, u1 - (r.topRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1, v1 - (r.bottomRight/bbox.height)*(v1-v0)); } else { _draw_rect_textured(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight, r.bottomRight, bbox.height-r.topRight-r.bottomRight, u1 - (r.bottomRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1, v1 - (r.bottomRight/bbox.height)*(v1-v0)); } } else { if(r.bottomRight < r.bottomLeft){ _draw_rect_textured(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, r.topRight-r.bottomRight, bbox.height-r.topRight-r.bottomLeft, u1 - (r.topRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1 - (r.bottomRight/bbox.width)*(u1-u0), v1 - (r.bottomLeft/bbox.height)*(v1-v0)); _draw_rect_textured(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight, r.bottomRight, bbox.height-r.topRight-r.bottomRight, u1 - (r.bottomRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1, v1 - (r.bottomRight/bbox.height)*(v1-v0)); } else { _draw_rect_textured(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight, r.topRight, bbox.height-r.topRight-r.bottomRight, u1 - (r.topRight/bbox.width)*(u1-u0), v0 + (r.topRight/bbox.height)*(v1-v0), u1, v1 - (r.bottomRight/bbox.height)*(v1-v0)); } } _draw_rect_textured(bbox.x+CLAY__MAX(r.topLeft, r.bottomLeft), bbox.y+CLAY__MAX(r.topLeft, r.topRight), bbox.width-CLAY__MAX(r.topLeft, r.bottomLeft)-CLAY__MAX(r.topRight, r.bottomRight), bbox.height-CLAY__MAX(r.topLeft, r.topRight)-CLAY__MAX(r.bottomLeft, r.bottomRight), u0+CLAY__MAX(r.topLeft,r.bottomLeft)/bbox.width*(u1-u0), v0+CLAY__MAX(r.topLeft,r.topRight)/bbox.height*(v1-v0), u1-CLAY__MAX(r.topRight,r.bottomRight)/bbox.width*(u1-u0), v1-CLAY__MAX(r.bottomLeft,r.bottomRight)/bbox.height*(v1-v0)); sgl_end(); sgl_disable_texture(); break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData *config = &renderCommand->renderData.border; sgl_c4f(config->color.r / 255.0f, config->color.g / 255.0f, config->color.b / 255.0f, config->color.a / 255.0f); Clay_BorderWidth w = config->width; Clay_CornerRadius r = config->cornerRadius; sgl_begin_triangle_strip(); if(w.left > 0){ _draw_rect(bbox.x, bbox.y + r.topLeft, w.left, bbox.height - r.topLeft - r.bottomLeft); } if(w.right > 0){ _draw_rect(bbox.x + bbox.width - w.right, bbox.y + r.topRight, w.right, bbox.height - r.topRight - r.bottomRight); } if(w.top > 0){ _draw_rect(bbox.x + r.topLeft, bbox.y, bbox.width - r.topLeft - r.topRight, w.top); } if(w.bottom > 0){ _draw_rect(bbox.x + r.bottomLeft, bbox.y + bbox.height - w.bottom, bbox.width - r.bottomLeft - r.bottomRight, w.bottom); } if(r.topLeft > 0 && (w.top > 0 || w.left > 0)){ _draw_corner_border(bbox.x, bbox.y, -r.topLeft, -r.topLeft, -r.topLeft+w.left, -r.topLeft+w.top); } if(r.topRight > 0 && (w.top > 0 || w.right > 0)){ _draw_corner_border(bbox.x+bbox.width, bbox.y, r.topRight, -r.topRight, r.topRight-w.right, -r.topRight+w.top); } if(r.bottomLeft > 0 && (w.bottom > 0 || w.left > 0)){ _draw_corner_border(bbox.x, bbox.y+bbox.height, -r.bottomLeft, r.bottomLeft, -r.bottomLeft+w.left, r.bottomLeft-w.bottom); } if(r.bottomRight > 0 && (w.bottom > 0 || w.right > 0)){ _draw_corner_border(bbox.x+bbox.width, bbox.y+bbox.height, r.bottomRight, r.bottomRight, r.bottomRight-w.right, r.bottomRight-w.bottom); } sgl_end(); break; } default: break; } } sgl_pop_pipeline(); sfons_flush(_sclay.fonts); } #endif /* SOKOL_CLAY_IMPL */ ================================================ FILE: renderers/termbox2/clay_renderer_termbox2.c ================================================ /* zlib/libpng license Copyright (c) 2025 Mivirl altered by Godje (Sep 2025) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include "../../clay.h" #include "image_character_masks.h" #define TB_OPT_ATTR_W 32 // Required for truecolor support #include "termbox2.h" #include "stb_image.h" #include "stb_image_resize2.h" // ------------------------------------------------------------------------------------------------- // -- Data structures typedef struct { int width, height; } clay_tb_dimensions; typedef struct { float width, height; } clay_tb_pixel_dimensions; typedef struct { int x, y; int width, height; } clay_tb_cell_bounding_box; typedef struct { Clay_Color clay; uintattr_t termbox; } clay_tb_color_pair; enum border_mode { CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_MODE_ROUND, CLAY_TB_BORDER_MODE_MINIMUM, }; enum border_chars { CLAY_TB_BORDER_CHARS_DEFAULT, CLAY_TB_BORDER_CHARS_ASCII, CLAY_TB_BORDER_CHARS_UNICODE, CLAY_TB_BORDER_CHARS_BLANK, CLAY_TB_BORDER_CHARS_NONE, }; enum image_mode { CLAY_TB_IMAGE_MODE_DEFAULT, CLAY_TB_IMAGE_MODE_PLACEHOLDER, CLAY_TB_IMAGE_MODE_BG, CLAY_TB_IMAGE_MODE_ASCII_FG, CLAY_TB_IMAGE_MODE_ASCII_FG_FAST, CLAY_TB_IMAGE_MODE_ASCII, CLAY_TB_IMAGE_MODE_ASCII_FAST, CLAY_TB_IMAGE_MODE_UNICODE, CLAY_TB_IMAGE_MODE_UNICODE_FAST, }; typedef struct { // Stores information about image loaded from stb int pixel_width, pixel_height; unsigned char *pixel_data; // Internal cached data from previous renders struct { enum image_mode last_image_mode; int width, height; size_t size_max; uint32_t *characters; Clay_Color *foreground; Clay_Color *background; // Data storing progress of partially complete image conversions that take multiple renders struct clay_tb_partial_render { bool in_progress; unsigned char *resized_pixel_data; int cursor_x, cursor_y; int cursor_mask; int min_difference_squared_sum; int best_mask; Clay_Color best_foreground, best_background; } partial_render; } internal; } clay_tb_image; // Truecolor is only enabled if TB_OPT_ATTR_W is set to 32 or 64. The default is 16, so it must be // defined to reference the constant #ifndef TB_OUTPUT_TRUECOLOR #define TB_OUTPUT_TRUECOLOR (TB_OUTPUT_GRAYSCALE + 1) #endif // Constant that doesn't collide with termbox2's existing output modes #define CLAY_TB_OUTPUT_NOCOLOR 0 #if !(defined NDEBUG || defined CLAY_TB_NDEBUG) #define clay_tb_assert(condition, ...) \ if (!(condition)) { \ Clay_Termbox_Close(); \ fprintf(stderr, "%s %d (%s): Assertion failure: ", __FILE__, __LINE__, __func__); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n"); \ exit(1); \ } #else #define clay_tb_assert(condition, ...) #endif // NDEBUG || CLAY_TB_NDEBUG // ------------------------------------------------------------------------------------------------- // -- Public API /** Set the equivalent size for a terminal cell in pixels. This size is used to convert Clay's pixel measurements to terminal cells, and affects scaling. Default dimensions were measured on Debian 12: (9, 21) \param width Width of a terminal cell in pixels \param height Height of a terminal cell in pixels */ void Clay_Termbox_Set_Cell_Pixel_Size(float width, float height); /** Sets the color rendering mode for the terminal \param color_mode Termbox output mode as defined in termbox2.h, excluding truecolor - TB_OUTPUT_NORMAL - Use default ANSI colors - TB_OUTPUT_256 - Use 256 terminal colors - TB_OUTPUT_216 - Use 216 terminal colors from 256 color mode - TB_OUTPUT_GRAYSCALE - Use 24 gray colors from 256 color mode - CLAY_TB_OUTPUT_NOCOLOR - Don't use ANSI colors at all */ void Clay_Termbox_Set_Color_Mode(int color_mode); /** Sets the method for converting the width of borders to terminal cells \param border_mode Method for adjusting border sizes to fit terminal cells - CLAY_TB_BORDER_MODE_DEFAULT - same as CLAY_TB_BORDER_MODE_MINIMUM - CLAY_TB_BORDER_MODE_ROUND - borders will be rounded to nearest cell size - CLAY_TB_BORDER_MODE_MINIMUM - borders will have a minimum width of one cell */ void Clay_Termbox_Set_Border_Mode(enum border_mode border_mode); /** Sets the character style to use for rendering borders \param border_chars Characters used for rendering borders - CLAY_TB_BORDER_CHARS_DEFAULT - same as BORDER_UNICODE - CLAY_TB_BORDER_CHARS_ASCII - Uses ascii characters: '+', '|', '-' - CLAY_TB_BORDER_CHARS_UNICODE - Uses unicode box drawing characters - CLAY_TB_BORDER_CHARS_BLANK - Draws background colors only - CLAY_TB_BORDER_CHARS_NONE - Don't draw borders */ void Clay_Termbox_Set_Border_Chars(enum border_chars border_chars); /** Sets the method for drawing images \param image_mode Method for adjusting border sizes to fit terminal cells - CLAY_TB_IMAGE_MODE_DEFAULT - same as CLAY_TB_IMAGE_MODE_UNICODE - CLAY_TB_IMAGE_MODE_PLACEHOLDER - Draw a placeholder pattern in place of images - CLAY_TB_IMAGE_MODE_BG - Draw image by setting the background color for space characters - CLAY_TB_IMAGE_MODE_ASCII_FG - Draw image by setting the foreground color for ascii characters - CLAY_TB_IMAGE_MODE_ASCII - Draw image by setting the foreground and background colors for ascii characters - CLAY_TB_IMAGE_MODE_UNICODE - Draw image by setting the foreground and background colors for unicode characters - CLAY_TB_IMAGE_MODE_ASCII_FG_FAST - Draw image by setting the foreground color for ascii characters. Checks fewer characters to draw faster - CLAY_TB_IMAGE_MODE_ASCII_FAST - Draw image by setting the foreground and background colors for ascii characters. Checks fewer characters to draw faster - CLAY_TB_IMAGE_MODE_UNICODE_FAST - Draw image by setting the foreground and background colors for unicode characters. Checks fewer characters to draw faster */ void Clay_Termbox_Set_Image_Mode(enum image_mode image_mode); /** Fuel corresponds to the amount of time spent per render on drawing images. Increasing this has the image render faster, but the program will be less responsive until it finishes Cost to draw one cell (lengths of arrays in image_character_masks.h): - 1 : CLAY_TB_IMAGE_MODE_BG - 15 : CLAY_TB_IMAGE_MODE_UNICODE_FAST, CLAY_TB_IMAGE_MODE_ASCII_FAST, CLAY_TB_IMAGE_MODE_ASCII_FG_FAST - 52 : CLAY_TB_IMAGE_MODE_UNICODE - 95 : CLAY_TB_IMAGE_MODE_ASCII, CLAY_TB_IMAGE_MODE_ASCII_FG \param fuel_max Maximum amount of fuel used per render (shared between all images) \param fuel_per_image Maximum amount of fuel used per render per image */ void Clay_Termbox_Set_Image_Fuel(int fuel_max, int fuel_per_image); /** Enables or disables emulated transparency If the color mode is TB_OUTPUT_NORMAL or CLAY_TB_OUTPUT_NOCOLOR, transparency will not be enabled \param transparency Transparency value to set */ void Clay_Termbox_Set_Transparency(bool transparency); /** Current width of the terminal in pixels */ float Clay_Termbox_Width(void); /** Current height of the terminal in pixels */ float Clay_Termbox_Height(void); /** Current width of a terminal cell in pixels */ float Clay_Termbox_Cell_Width(void); /** Current height of a terminal cell in pixels */ float Clay_Termbox_Cell_Height(void); /** Callback function used to measure the dimensions in pixels of a text string \param text Text to measure \param config Ignored \param userData Ignored */ static inline Clay_Dimensions Clay_Termbox_MeasureText( Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); /** Load an image from a file into a format usable with this renderer Supports image formats from stb_image (JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC) Note that rendered characters are cached in the returned `clay_tb_image`. If the same image is used in multiple places, load it a separate time for each use to reduce unecessary reprocessing every render. \param filename File to load image from */ clay_tb_image Clay_Termbox_Image_Load_File(const char *filename); /** Load an image from memory into a format usable with this renderer Supports image formats from stb_image (JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC) Note that rendered characters are cached in the returned `clay_tb_image`. If the same image is used in multiple places, load it a separate time for each use to reduce unecessary reprocessing every render. \param image Image to load. Should be the whole file copied into memory \param size Size of the image in bytes */ clay_tb_image Clay_Termbox_Image_Load_Memory(const void *image, int size); /** Free an image \param image Image to free */ void Clay_Termbox_Image_Free(clay_tb_image *image); /** Set up configuration, start termbox2, and allocate internal structures. Configuration can be overriden by environment variables: - CLAY_TB_COLOR_MODE - NORMAL - 256 - 216 - GRAYSCALE - TRUECOLOR - NOCOLOR - CLAY_TB_BORDER_CHARS - DEFAULT - ASCII - UNICODE - BLANK - NONE - CLAY_TB_IMAGE_MODE - DEFAULT - PLACEHOLDER - BG - ASCII_FG - ASCII - UNICODE - ASCII_FG_FAST - ASCII_FAST - UNICODE_FAST - CLAY_TB_TRANSPARENCY - 1 - 0 - CLAY_TB_CELL_PIXELS - 10x20 Must be run before using this renderer. \param color_mode Termbox output mode as defined in termbox2.h, excluding truecolor \param border_mode Method for adjusting border sizes to fit terminal cells \param border_chars Characters used for rendering borders \param image_mode Method for drawing images \param transparency Emulate transparency using background colors */ void Clay_Termbox_Initialize(int color_mode, enum border_mode border_mode, enum border_chars border_chars, enum image_mode image_mode, bool transparency); /** Stop termbox2 and release internal structures */ void Clay_Termbox_Close(void); /** Render a set of commands to the terminal \param commands Array of render commands from Clay's CreateLayout() function */ void Clay_Termbox_Render(Clay_RenderCommandArray commands); /** Convenience function to block until an event is received from termbox. If an image is only partially rendered, this returns immediately. */ void Clay_Termbox_Waitfor_Event(void); // ------------------------------------------------------------------------------------------------- // -- Internal state // Settings/options static bool clay_tb_initialized = false; static int clay_tb_color_mode = TB_OUTPUT_NORMAL; static bool clay_tb_transparency = false; static enum border_mode clay_tb_border_mode = CLAY_TB_BORDER_MODE_DEFAULT; static enum border_chars clay_tb_border_chars = CLAY_TB_BORDER_CHARS_DEFAULT; static enum image_mode clay_tb_image_mode = CLAY_TB_IMAGE_MODE_DEFAULT; // Dimensions of a cell are specified in pixels // Default dimensions were measured from the default terminal on Debian 12: // Terminal: gnome-terminal // Font: "Monospace Regular" // Font size: 11 static clay_tb_pixel_dimensions clay_tb_cell_size = { .width = 9, .height = 21 }; // Scissor mode prevents drawing outside of the specified bounding box static bool clay_tb_scissor_enabled = false; clay_tb_cell_bounding_box clay_tb_scissor_box; // Images may be drawn across multiple renders to improve responsiveness. The initial draw will be // approximate, then further partial draws will replace characters with more accurate ones static bool clay_tb_partial_image_drawn = false; // Maximum fuel used per render across all images static int clay_tb_image_fuel_max = 200 * 1024; // Maximum fuel used per render per image static int clay_tb_image_fuel_per_image = 100 * 1024; // Fuel used this render static int clay_tb_image_fuel_used = 0; // ----------------------------------------------- // -- Color buffer // Buffer storing background colors from previously drawn items. Used to emulate transparency and // set the background color for text. static Clay_Color *clay_tb_color_buffer_clay = NULL; // Dimensions are specified in cells static clay_tb_dimensions clay_tb_color_buffer_dimensions = { 0, 0 }; static clay_tb_dimensions clay_tb_color_buffer_max_dimensions = { 0, 0 }; // ------------------------------------------------------------------------------------------------- // -- Internal utility functions static inline bool clay_tb_valid_color(Clay_Color color) { return ( 0x00 <= color.r && color.r <= 0xff && 0x00 <= color.g && color.g <= 0xff && 0x00 <= color.b && color.b <= 0xff && 0x00 <= color.a && color.a <= 0xff ); } /** In 256-color mode, there are 216 colors (excluding default terminal colors and gray colors), with 6 different magnitudes for each of r, g, b. This function clamps to the nearest intensity (represented by 0-5) that can be output in this mode Possible intensities per component: 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff Examples: - 0x20 -> 0 - 0x2f -> 1 - 0x85 -> 2 - 0xff -> 5 \param color 8-bit intensity of one RGB component */ static int clay_tb_rgb_intensity_to_index(int color) { clay_tb_assert(0x00 <= color && color <= 0xff, "Invalid intensity (allowed range 0x00-0xff)"); return (color < 0x2f) ? 0 : (color < 0x73) ? 1 : 2 + ((color - 0x73) / 0x28); } /** Convert an RGB color from Clay's representation to the nearest representable color in the current termbox2 output mode \param color Color to convert */ static uintattr_t clay_tb_color_convert(Clay_Color color) { clay_tb_assert(clay_tb_valid_color(color), "Invalid Clay color: (%f, %f, %f, %f)", color.r, color.g, color.b, color.a); uintattr_t tb_color = TB_DEFAULT; switch (clay_tb_color_mode) { default: { clay_tb_assert(false, "Invalid or unimplemented Termbox color output mode (%d)", clay_tb_color_mode); break; } case TB_OUTPUT_NORMAL: { const int color_lut_count = 16; const uintattr_t color_lut[][4] = { { TB_BLACK, 0x00, 0x00, 0x00 }, { TB_RED, 0xaa, 0x00, 0x00 }, { TB_GREEN, 0x00, 0xaa, 0x00 }, { TB_YELLOW, 0xaa, 0x55, 0x00 }, { TB_BLUE, 0x00, 0x00, 0xaa }, { TB_MAGENTA, 0xaa, 0x00, 0xaa }, { TB_CYAN, 0x00, 0xaa, 0xaa }, { TB_WHITE, 0xaa, 0xaa, 0xaa }, { TB_BLACK | TB_BRIGHT, 0x55, 0x55, 0x55 }, { TB_RED | TB_BRIGHT, 0xff, 0x55, 0x55 }, { TB_GREEN | TB_BRIGHT, 0x55, 0xff, 0x55 }, { TB_YELLOW | TB_BRIGHT, 0xff, 0xff, 0x55 }, { TB_BLUE | TB_BRIGHT, 0x55, 0x55, 0xff }, { TB_MAGENTA | TB_BRIGHT, 0xff, 0x55, 0xff }, { TB_CYAN | TB_BRIGHT, 0x55, 0xff, 0xff }, { TB_WHITE | TB_BRIGHT, 0xff, 0xff, 0xff } }; // Find nearest color on the lookup table int color_index = 0; float min_distance_squared = 0xff * 0xff * 3; for (int i = 0; i < color_lut_count; ++i) { float r_distance = color.r - (float)color_lut[i][1]; float g_distance = color.g - (float)color_lut[i][2]; float b_distance = color.b - (float)color_lut[i][3]; float distance_squared = (r_distance * r_distance) + (g_distance * g_distance) + (b_distance * b_distance); // Penalize pure black and white to display faded colors more often if (TB_BLACK == color_lut[i][0] || TB_WHITE == color_lut[i][0] || (TB_BLACK | TB_BRIGHT) == color_lut[i][0] || (TB_WHITE | TB_BRIGHT) == color_lut[i][0]) { distance_squared *= 2; } if (distance_squared < min_distance_squared) { min_distance_squared = distance_squared; color_index = i; } } tb_color = color_lut[color_index][0]; break; } case TB_OUTPUT_216: { int r_index = clay_tb_rgb_intensity_to_index((int)color.r); int g_index = clay_tb_rgb_intensity_to_index((int)color.g); int b_index = clay_tb_rgb_intensity_to_index((int)color.b); tb_color = 0x01 + (36 * r_index) + (6 * g_index) + (b_index); break; } case TB_OUTPUT_256: { const int index_lut_count = 6; const uintattr_t index_lut[] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff }; int r_index = clay_tb_rgb_intensity_to_index((int)color.r); int g_index = clay_tb_rgb_intensity_to_index((int)color.g); int b_index = clay_tb_rgb_intensity_to_index((int)color.b); int rgb_color = 0x10 + (36 * r_index) + (6 * g_index) + (b_index); float rgb_r_distance = color.r - (float)index_lut[r_index]; float rgb_g_distance = color.g - (float)index_lut[g_index]; float rgb_b_distance = color.b - (float)index_lut[b_index]; float rgb_distance_squared = (rgb_r_distance * rgb_r_distance) + (rgb_g_distance * rgb_g_distance) + (rgb_b_distance * rgb_b_distance); int avg_color = (int)((color.r + color.g + color.b) / 3); int gray_avg_color = (avg_color * 24 / 0x100); int gray_color = 0xe8 + gray_avg_color; float gray_r_distance = color.r - (float)gray_avg_color; float gray_g_distance = color.g - (float)gray_avg_color; float gray_b_distance = color.b - (float)gray_avg_color; float gray_distance_squared = (gray_r_distance * gray_r_distance) + (gray_g_distance * gray_g_distance) + (gray_b_distance * gray_b_distance); tb_color = (rgb_distance_squared < gray_distance_squared) ? rgb_color : gray_color; break; } case TB_OUTPUT_GRAYSCALE: { // 24 shades of gray float avg_color = ((color.r + color.g + color.b) / 3); tb_color = 0x01 + (int)(avg_color * 24 / 0x100); break; } case TB_OUTPUT_TRUECOLOR: { clay_tb_assert(32 <= TB_OPT_ATTR_W, "Truecolor requires TB_OPT_ATTR_W to be 32 or 64"); tb_color = ((uintattr_t)color.r << 4 * 4) + ((uintattr_t)color.g << 2 * 4) + ((uintattr_t)color.b); if (0x000000 == tb_color) { tb_color = TB_HI_BLACK; } break; } case CLAY_TB_OUTPUT_NOCOLOR: { // Uses default terminal colors tb_color = TB_DEFAULT; break; } } return tb_color; } /** Round float to nearest integer value Used instead of roundf() so math.h doesn't need to be linked \param f Float to round */ static inline int clay_tb_roundf(float f) { int i = f; return (f - i > 0.5f) ? i + 1 : i; } /** Snap pixel values from Clay to nearest cell values Width/height accounts for offset from x/y, so a box at x=(1.2 * cell_width) and width=(1.4 * cell_width) is snapped to x=1 and width=2. \param box Bounding box with pixel measurements to convert */ static inline clay_tb_cell_bounding_box cell_snap_bounding_box(Clay_BoundingBox box) { return (clay_tb_cell_bounding_box) { .x = clay_tb_roundf(box.x / clay_tb_cell_size.width), .y = clay_tb_roundf(box.y / clay_tb_cell_size.height), .width = clay_tb_roundf((box.x + box.width) / clay_tb_cell_size.width) - clay_tb_roundf(box.x / clay_tb_cell_size.width), .height = clay_tb_roundf((box.y + box.height) / clay_tb_cell_size.height) - clay_tb_roundf(box.y / clay_tb_cell_size.height), }; } /** Snap pixel values from Clay to nearest cell values without considering x and y position when calculating width/height. Width/height ignores offset from x/y, so a box at x=(1.2 * cell_width) and width=(1.4 * cell_width) is snapped to x=1 and width=1. \param box Bounding box with pixel measurements to convert */ static inline clay_tb_cell_bounding_box cell_snap_pos_ind_bounding_box(Clay_BoundingBox box) { return (clay_tb_cell_bounding_box) { .x = clay_tb_roundf(box.x / clay_tb_cell_size.width), .y = clay_tb_roundf(box.y / clay_tb_cell_size.height), .width = clay_tb_roundf(box.width / clay_tb_cell_size.width), .height = clay_tb_roundf(box.height / clay_tb_cell_size.height), }; } /** Get stored clay color for a position from the internal color buffer \param x X position of cell \param y Y position of cell */ static inline Clay_Color clay_tb_color_buffer_clay_get(int x, int y) { clay_tb_assert(0 <= x && x < clay_tb_color_buffer_dimensions.width, "Cell buffer x position (%d) offscreen (range 0-%d)", x, clay_tb_color_buffer_dimensions.width); clay_tb_assert(0 <= y && y < clay_tb_color_buffer_dimensions.height, "Cell buffer y position (%d) offscreen (range 0-%d)", y, clay_tb_color_buffer_dimensions.height); return clay_tb_color_buffer_clay[x + (y * clay_tb_color_buffer_dimensions.width)]; } /** Set stored clay color for a position in the internal color buffer \param x X position of cell \param y Y position of cell \param color Color to store */ static inline void clay_tb_color_buffer_clay_set(int x, int y, Clay_Color color) { clay_tb_assert(0 <= x && x < clay_tb_color_buffer_dimensions.width, "Cell buffer x position (%d) offscreen (range 0-%d)", x, clay_tb_color_buffer_dimensions.width); clay_tb_assert(0 <= y && y < clay_tb_color_buffer_dimensions.height, "Cell buffer y position (%d) offscreen (range 0-%d)", y, clay_tb_color_buffer_dimensions.height); clay_tb_color_buffer_clay[x + (y * clay_tb_color_buffer_dimensions.width)] = color; } /** Resize internal color buffer to the current terminal size */ static void clay_tb_resize_buffer(void) { int current_width = tb_width(); int current_height = tb_height(); // Reallocate if the new size is larger than the maximum size of the buffer size_t max_size = (size_t)clay_tb_color_buffer_max_dimensions.width * clay_tb_color_buffer_max_dimensions.height; size_t new_size = (size_t)current_width * current_height; if (max_size < new_size) { Clay_Color *tmp_clay = tb_realloc(clay_tb_color_buffer_clay, sizeof(Clay_Color) * new_size); if (NULL == tmp_clay) { clay_tb_assert(false, "Reallocation failure for internal clay color buffer"); } clay_tb_color_buffer_clay = tmp_clay; for (size_t i = max_size; i < new_size; ++i) { clay_tb_color_buffer_clay[i] = (Clay_Color) { 0 }; } clay_tb_color_buffer_max_dimensions.width = current_width; clay_tb_color_buffer_max_dimensions.height = current_height; } clay_tb_color_buffer_dimensions.width = current_width; clay_tb_color_buffer_dimensions.height = current_height; } /** Calculate color at a given position after emulating transparency. This isn't true transparency, just the background colors changing to emulate it \param color Pair of termbox and clay color representations to overlay with the background color \param x X position of cell \param y Y position of cell */ static inline clay_tb_color_pair clay_tb_get_transparency_color( int x, int y, clay_tb_color_pair color) { if (!clay_tb_transparency) { return color; } Clay_Color color_bg = clay_tb_color_buffer_clay_get(x, y); Clay_Color new_color = { .r = color_bg.r + (color.clay.a / 255) * (color.clay.r - color_bg.r), .g = color_bg.g + (color.clay.a / 255) * (color.clay.g - color_bg.g), .b = color_bg.b + (color.clay.a / 255) * (color.clay.b - color_bg.b), .a = 255 }; return (clay_tb_color_pair) { .clay = new_color, .termbox = clay_tb_color_convert(new_color) }; } /** Draw a character cell at a position on screen. Accounts for scissor mode and stores the cell to the internal color buffer for transparency and text backgrounds. \param x X position of cell \param y Y position of cell \param ch Utf32 representation of character to draw \param tb_fg Foreground color in termbox representation \param tb_bg Background color in termbox representation \param fg Foreground color in clay representation \param bg Background color in clay representation */ static int clay_tb_set_cell( int x, int y, uint32_t ch, uintattr_t tb_fg, uintattr_t tb_bg, Clay_Color bg) { clay_tb_assert(0 <= x && x < tb_width(), "Cell buffer x position (%d) offscreen (range 0-%d)", x, tb_width()); clay_tb_assert(0 <= y && y < tb_height(), "Cell buffer y position (%d) offscreen (range 0-%d)", y, tb_height()); if (!clay_tb_scissor_enabled || (clay_tb_scissor_enabled && (clay_tb_scissor_box.x <= x && x < clay_tb_scissor_box.x + clay_tb_scissor_box.width) && (clay_tb_scissor_box.y <= y && y < clay_tb_scissor_box.y + clay_tb_scissor_box.height))) { int codepoint_width = tb_wcwidth(ch); if (-1 == codepoint_width) { // Nonprintable character, use REPLACEMENT CHARACTER (U+FFFD) ch = U'\ufffd'; codepoint_width = tb_wcwidth(ch); } int err; int max_x = CLAY__MIN(x + codepoint_width, tb_width()); for (int i = x; i < max_x; ++i) { clay_tb_color_buffer_clay_set(i, y, bg); err = tb_set_cell(i, y, ch, tb_fg, tb_bg); if (TB_OK != err) { break; } } return err; } return -1; } /** Convert a pixel-based image to a cell-based image of the specified width and height. Stores the converted/resized result in the cache of the input image. If the image has not changed size or image mode since the last convert it is returned unchanged \param image Image to convert/resize \param width Target width in cells for the converted image \param height Target height in cells for the converted image */ bool clay_tb_image_convert(clay_tb_image *image, int width, int height) { clay_tb_assert(NULL != image->pixel_data, "Image must be loaded"); bool image_unchanged = (width == image->internal.width && height == image->internal.height && (clay_tb_image_mode == image->internal.last_image_mode)); if (image_unchanged && !image->internal.partial_render.in_progress) { return true; } if (!image_unchanged) { free(image->internal.partial_render.resized_pixel_data); image->internal.partial_render = (struct clay_tb_partial_render) { .in_progress = false, .resized_pixel_data = NULL, .cursor_x = 0, .cursor_y = 0, .cursor_mask = 0, .min_difference_squared_sum = INT_MAX, .best_mask = 0, .best_foreground = { 0, 0, 0, 0 }, .best_background = { 0, 0, 0, 0 } }; } const size_t size = (size_t)width * height; // Allocate/resize internal cache data if (size > image->internal.size_max) { uint32_t *tmp_characters = realloc(image->internal.characters, size * sizeof(uint32_t)); Clay_Color *tmp_foreground = realloc(image->internal.foreground, size * sizeof(Clay_Color)); Clay_Color *tmp_background = realloc(image->internal.background, size * sizeof(Clay_Color)); if (NULL == tmp_characters || NULL == tmp_foreground || NULL == tmp_background) { image->internal.size_max = 0; free(tmp_characters); free(tmp_foreground); free(tmp_background); image->internal.characters = NULL; image->internal.foreground = NULL; image->internal.background = NULL; return false; } image->internal.characters = tmp_characters; image->internal.foreground = tmp_foreground; image->internal.background = tmp_background; image->internal.size_max = size; } image->internal.width = width; image->internal.height = height; // Resize image using the same width/height in cells, but with the pixel sizes of the character // masks instead of the cell size. The pixel data for each character mask will be compared to // the pixel data of a small section of the image under the mask. The closest mask to the image // data is chosen as the character to draw. const int character_mask_pixel_width = 6; const int character_mask_pixel_height = 12; const int pixel_width = width * character_mask_pixel_width; const int pixel_height = height * character_mask_pixel_height; unsigned char *resized_pixel_data; if (image->internal.partial_render.in_progress) { resized_pixel_data = image->internal.partial_render.resized_pixel_data; } else { resized_pixel_data = stbir_resize_uint8_linear(image->pixel_data, image->pixel_width, image->pixel_height, 0, NULL, pixel_width, pixel_height, 0, STBIR_RGB); image->internal.partial_render.resized_pixel_data = resized_pixel_data; } int num_character_masks = 1; const clay_tb_character_mask *character_masks = NULL; switch (clay_tb_image_mode) { case CLAY_TB_IMAGE_MODE_BG: { num_character_masks = 1; character_masks = &clay_tb_image_shapes_ascii_fast[0]; break; } case CLAY_TB_IMAGE_MODE_ASCII: case CLAY_TB_IMAGE_MODE_ASCII_FG: { num_character_masks = CLAY_TB_IMAGE_SHAPES_ASCII_BEST_COUNT; character_masks = &clay_tb_image_shapes_ascii_best[0]; break; } case CLAY_TB_IMAGE_MODE_UNICODE: { num_character_masks = CLAY_TB_IMAGE_SHAPES_UNICODE_BEST_COUNT; character_masks = &clay_tb_image_shapes_unicode_best[0]; break; } case CLAY_TB_IMAGE_MODE_ASCII_FAST: case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: { num_character_masks = CLAY_TB_IMAGE_SHAPES_ASCII_FAST_COUNT; character_masks = &clay_tb_image_shapes_ascii_fast[0]; break; } case CLAY_TB_IMAGE_MODE_UNICODE_FAST: { num_character_masks = CLAY_TB_IMAGE_SHAPES_UNICODE_FAST_COUNT; character_masks = &clay_tb_image_shapes_unicode_fast[0]; break; } }; // The number of character masks to check before exiting the render for this step // Used to improve responsiveness by splitting renders across multiple frames const int fuel_amount_initial = CLAY__MIN(clay_tb_image_fuel_per_image, clay_tb_image_fuel_max - clay_tb_image_fuel_used); int fuel_remaining = fuel_amount_initial; bool partial_character_render = false; // Do a quick initial render to set the background if (!image->internal.partial_render.in_progress) { image->internal.last_image_mode = clay_tb_image_mode; for (int y = image->internal.partial_render.cursor_y; y < height; ++y) { for (int x = image->internal.partial_render.cursor_x; x < width; ++x) { const int cell_top_left_pixel_x = x * character_mask_pixel_width; const int cell_top_left_pixel_y = y * character_mask_pixel_height; const int image_index = 3 * (((cell_top_left_pixel_y + character_mask_pixel_height / 2) * pixel_width) + (cell_top_left_pixel_x + character_mask_pixel_width / 2)); Clay_Color pixel_color = { (float)resized_pixel_data[image_index], (float)resized_pixel_data[image_index + 1], (float)resized_pixel_data[image_index + 2], }; const int cell_index = y * width + x; image->internal.characters[cell_index] = '.'; image->internal.foreground[cell_index] = pixel_color; image->internal.background[cell_index] = pixel_color; fuel_remaining = CLAY__MAX(0, fuel_remaining - 1); } } } if (0 == fuel_remaining) { image->internal.partial_render.in_progress = true; clay_tb_partial_image_drawn = true; goto done; } for (int y = image->internal.partial_render.cursor_y; y < height; ++y) { for (int x = image->internal.partial_render.cursor_x; x < width; ++x) { const int cell_top_left_pixel_x = x * character_mask_pixel_width; const int cell_top_left_pixel_y = y * character_mask_pixel_height; // For each possible cell character, use the mask to find the average color for the // foreground ('1's) and background ('0's). int min_difference_squared_sum = image->internal.partial_render.min_difference_squared_sum; int best_mask = image->internal.partial_render.best_mask; Clay_Color best_foreground = image->internal.partial_render.best_foreground; Clay_Color best_background = image->internal.partial_render.best_background; for (int i = image->internal.partial_render.cursor_mask; i < num_character_masks; ++i) { int color_avg_background_r = 0; int color_avg_background_g = 0; int color_avg_background_b = 0; int color_avg_foreground_r = 0; int color_avg_foreground_g = 0; int color_avg_foreground_b = 0; int foreground_count = 0; int background_count = 0; for (int cell_pixel_y = 0; cell_pixel_y < character_mask_pixel_height; ++cell_pixel_y) { for (int cell_pixel_x = 0; cell_pixel_x < character_mask_pixel_width; ++cell_pixel_x) { const int index = 3 * (((cell_top_left_pixel_y + cell_pixel_y) * pixel_width) + (cell_top_left_pixel_x + cell_pixel_x)); const int mask_index = (cell_pixel_y * character_mask_pixel_width) + cell_pixel_x; if (0 == character_masks[i].data[mask_index]) { if (CLAY_TB_IMAGE_MODE_ASCII_FG != clay_tb_image_mode && CLAY_TB_IMAGE_MODE_ASCII_FG_FAST != clay_tb_image_mode) { color_avg_background_r += resized_pixel_data[index]; color_avg_background_g += resized_pixel_data[index + 1]; color_avg_background_b += resized_pixel_data[index + 2]; background_count += 1; } } else { color_avg_foreground_r += resized_pixel_data[index]; color_avg_foreground_g += resized_pixel_data[index + 1]; color_avg_foreground_b += resized_pixel_data[index + 2]; foreground_count += 1; } } } if (CLAY_TB_IMAGE_MODE_ASCII_FG != clay_tb_image_mode && CLAY_TB_IMAGE_MODE_ASCII_FG_FAST != clay_tb_image_mode) { color_avg_background_r /= CLAY__MAX(1, background_count); color_avg_background_g /= CLAY__MAX(1, background_count); color_avg_background_b /= CLAY__MAX(1, background_count); } else { color_avg_background_r = 0; color_avg_background_g = 0; color_avg_background_b = 0; } color_avg_foreground_r /= CLAY__MAX(1, foreground_count); color_avg_foreground_g /= CLAY__MAX(1, foreground_count); color_avg_foreground_b /= CLAY__MAX(1, foreground_count); // Determine the difference between the mask with colors and the actual pixel data int difference_squared_sum = 0; for (int cell_pixel_y = 0; cell_pixel_y < character_mask_pixel_height; ++cell_pixel_y) { for (int cell_pixel_x = 0; cell_pixel_x < character_mask_pixel_width; ++cell_pixel_x) { const int index = 3 * (((cell_top_left_pixel_y + cell_pixel_y) * pixel_width) + (cell_top_left_pixel_x + cell_pixel_x)); int rdiff, gdiff, bdiff, adiff; const int mask_index = (cell_pixel_y * character_mask_pixel_width) + cell_pixel_x; if (0 == character_masks[i].data[mask_index]) { rdiff = (color_avg_background_r - resized_pixel_data[index]); gdiff = (color_avg_background_g - resized_pixel_data[index + 1]); bdiff = (color_avg_background_b - resized_pixel_data[index + 2]); } else { rdiff = (color_avg_foreground_r - resized_pixel_data[index]); gdiff = (color_avg_foreground_g - resized_pixel_data[index + 1]); bdiff = (color_avg_foreground_b - resized_pixel_data[index + 2]); } difference_squared_sum += ( (rdiff * rdiff) + (gdiff * gdiff) + (bdiff * bdiff)); } } // Choose the closest character mask to the image data if (difference_squared_sum < min_difference_squared_sum) { min_difference_squared_sum = difference_squared_sum; best_mask = i; best_background = (Clay_Color) { .r = (float)color_avg_background_r, .g = (float)color_avg_background_g, .b = (float)color_avg_background_b, .a = 255 }; best_foreground = (Clay_Color) { .r = (float)color_avg_foreground_r, .g = (float)color_avg_foreground_g, .b = (float)color_avg_foreground_b, .a = 255 }; } fuel_remaining -= 1; if (0 == fuel_remaining) { // Set progress for partial render image->internal.partial_render = (struct clay_tb_partial_render) { .in_progress = true, .resized_pixel_data = resized_pixel_data, .cursor_x = x, .cursor_y = y, .cursor_mask = i + 1, .min_difference_squared_sum = min_difference_squared_sum, .best_mask = best_mask, .best_foreground = best_foreground, .best_background = best_background }; partial_character_render = true; clay_tb_partial_image_drawn = true; goto done; } } image->internal.partial_render.cursor_mask = 0; // Set data in cache for this character const int index = y * width + x; image->internal.characters[index] = character_masks[best_mask].character; image->internal.foreground[index] = best_foreground; image->internal.background[index] = best_background; image->internal.partial_render = (struct clay_tb_partial_render) { .in_progress = true, .resized_pixel_data = resized_pixel_data, .cursor_x = x + 1, .cursor_y = y, .cursor_mask = 0, .min_difference_squared_sum = INT_MAX, .best_mask = 0, .best_foreground = { 0, 0, 0, 0 }, .best_background = { 0, 0, 0, 0 }, }; if (0 == fuel_remaining) { clay_tb_partial_image_drawn = true; goto done; } } image->internal.partial_render.cursor_x = 0; } image->internal.partial_render.cursor_y = 0; image->internal.partial_render.in_progress = false; free(resized_pixel_data); image->internal.partial_render.resized_pixel_data = NULL; done: clay_tb_image_fuel_used += fuel_amount_initial - fuel_remaining; return true; } // ------------------------------------------------------------------------------------------------- // -- Public API implementation void Clay_Termbox_Set_Cell_Pixel_Size(float width, float height) { clay_tb_assert(0 <= width, "Cell pixel width must be > 0"); clay_tb_assert(0 <= height, "Cell pixel height must be > 0"); clay_tb_cell_size = (clay_tb_pixel_dimensions) { .width = width, .height = height }; } void Clay_Termbox_Set_Color_Mode(int color_mode) { clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); clay_tb_assert(CLAY_TB_OUTPUT_NOCOLOR <= color_mode && color_mode <= TB_OUTPUT_TRUECOLOR, "Color mode invalid (%d)", color_mode); if (CLAY_TB_OUTPUT_NOCOLOR == color_mode) { tb_set_output_mode(TB_OUTPUT_NORMAL); } else { tb_set_output_mode(color_mode); } // Force complete re-render to ensure all colors are redrawn tb_invalidate(); clay_tb_color_mode = color_mode; // Re-set transparency value. It will be toggled off if the new output mode doesn't support it Clay_Termbox_Set_Transparency(clay_tb_transparency); } void Clay_Termbox_Set_Border_Mode(enum border_mode border_mode) { clay_tb_assert(CLAY_TB_BORDER_MODE_DEFAULT <= border_mode && border_mode <= CLAY_TB_BORDER_MODE_MINIMUM, "Border mode invalid (%d)", border_mode); if (CLAY_TB_BORDER_MODE_DEFAULT == border_mode) { clay_tb_border_mode = CLAY_TB_BORDER_MODE_MINIMUM; } else { clay_tb_border_mode = border_mode; } } void Clay_Termbox_Set_Border_Chars(enum border_chars border_chars) { clay_tb_assert( CLAY_TB_BORDER_CHARS_DEFAULT <= border_chars && border_chars <= CLAY_TB_BORDER_CHARS_NONE, "Border mode invalid (%d)", border_chars); if (CLAY_TB_BORDER_CHARS_DEFAULT == border_chars) { clay_tb_border_chars = CLAY_TB_BORDER_CHARS_UNICODE; } else { clay_tb_border_chars = border_chars; } } void Clay_Termbox_Set_Image_Mode(enum image_mode image_mode) { clay_tb_assert(CLAY_TB_IMAGE_MODE_DEFAULT <= image_mode && image_mode <= CLAY_TB_IMAGE_MODE_UNICODE_FAST, "Image mode invalid (%d)", image_mode); if (CLAY_TB_IMAGE_MODE_DEFAULT == image_mode) { clay_tb_image_mode = CLAY_TB_IMAGE_MODE_UNICODE; } else { clay_tb_image_mode = image_mode; } } void Clay_Termbox_Set_Image_Fuel(int fuel_max, int fuel_per_image) { clay_tb_assert(0 < fuel_max && 0 < fuel_per_image, "Fuel must be positive (%d, %d)", fuel_max, fuel_per_image); clay_tb_image_fuel_max = fuel_max; clay_tb_image_fuel_per_image = fuel_per_image; } void Clay_Termbox_Set_Transparency(bool transparency) { clay_tb_transparency = transparency; if (TB_OUTPUT_NORMAL == clay_tb_color_mode || CLAY_TB_OUTPUT_NOCOLOR == clay_tb_color_mode) { clay_tb_transparency = false; } } float Clay_Termbox_Width(void) { clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); return (float)tb_width() * clay_tb_cell_size.width; } float Clay_Termbox_Height(void) { clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); return (float)tb_height() * clay_tb_cell_size.height; } float Clay_Termbox_Cell_Width(void) { return clay_tb_cell_size.width; } float Clay_Termbox_Cell_Height(void) { return clay_tb_cell_size.height; } static inline Clay_Dimensions Clay_Termbox_MeasureText( Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); int width = 0; int height = 1; // Convert to utf32 so termbox2's internal wcwidth function can get the printed width of each // codepoint for (int32_t i = 0; i < text.length;) { uint32_t ch; int codepoint_bytes = tb_utf8_char_to_unicode(&ch, text.chars + i); if (0 > codepoint_bytes) { clay_tb_assert(false, "Invalid utf8"); } i += codepoint_bytes; int codepoint_width = tb_wcwidth(ch); if (-1 == codepoint_width) { // Nonprintable character, use width of REPLACEMENT CHARACTER (U+FFFD) codepoint_width = tb_wcwidth(0xfffd); } width += codepoint_width; } return (Clay_Dimensions) { (float)width * clay_tb_cell_size.width, (float)height * clay_tb_cell_size.height }; } clay_tb_image Clay_Termbox_Image_Load_File(const char *filename) { clay_tb_assert(NULL != filename, "Filename cannot be null"); clay_tb_image rv = { 0 }; FILE *image_file = NULL; image_file = fopen(filename, "r"); if (NULL == image_file) { fprintf(stderr, "Failed to open image %s: %s\n", filename, strerror(errno)); return rv; } int channels_in_file; const int desired_color_channels = 3; rv.pixel_data = stbi_load_from_file( image_file, &rv.pixel_width, &rv.pixel_height, &channels_in_file, desired_color_channels); fclose(image_file); return rv; } clay_tb_image Clay_Termbox_Image_Load_Memory(const void *image, int size) { clay_tb_assert(NULL != image, "Image cannot be null"); clay_tb_assert(0 < size, "Image size must be > 0"); clay_tb_image rv = { 0 }; int channels_in_file; const int desired_color_channels = 3; rv.pixel_data = stbi_load_from_memory( image, size, &rv.pixel_width, &rv.pixel_height, &channels_in_file, desired_color_channels); return rv; } void Clay_Termbox_Image_Free(clay_tb_image *image) { free(image->pixel_data); free(image->internal.partial_render.resized_pixel_data); free(image->internal.characters); free(image->internal.foreground); free(image->internal.background); *image = (clay_tb_image) { 0 }; } void Clay_Termbox_Initialize(int color_mode, enum border_mode border_mode, enum border_chars border_chars, enum image_mode image_mode, bool transparency) { int new_color_mode = color_mode; int new_border_mode = border_mode; int new_border_chars = border_chars; int new_image_mode = image_mode; int new_transparency = transparency; clay_tb_pixel_dimensions new_pixel_size = clay_tb_cell_size; // Check for environment variables that override settings const char *env_color_mode = getenv("CLAY_TB_COLOR_MODE"); if (NULL != env_color_mode) { if (0 == strcmp("NORMAL", env_color_mode)) { new_color_mode = TB_OUTPUT_NORMAL; } else if (0 == strcmp("256", env_color_mode)) { new_color_mode = TB_OUTPUT_256; } else if (0 == strcmp("216", env_color_mode)) { new_color_mode = TB_OUTPUT_216; } else if (0 == strcmp("GRAYSCALE", env_color_mode)) { new_color_mode = TB_OUTPUT_GRAYSCALE; } else if (0 == strcmp("TRUECOLOR", env_color_mode)) { new_color_mode = TB_OUTPUT_TRUECOLOR; } else if (0 == strcmp("NOCOLOR", env_color_mode)) { new_color_mode = CLAY_TB_OUTPUT_NOCOLOR; } } const char *env_border_chars = getenv("CLAY_TB_BORDER_CHARS"); if (NULL != env_border_chars) { if (0 == strcmp("DEFAULT", env_border_chars)) { new_border_chars = CLAY_TB_BORDER_CHARS_DEFAULT; } else if (0 == strcmp("ASCII", env_border_chars)) { new_border_chars = CLAY_TB_BORDER_CHARS_ASCII; } else if (0 == strcmp("UNICODE", env_border_chars)) { new_border_chars = CLAY_TB_BORDER_CHARS_UNICODE; } else if (0 == strcmp("BLANK", env_border_chars)) { new_border_chars = CLAY_TB_BORDER_CHARS_BLANK; } else if (0 == strcmp("NONE", env_border_chars)) { new_border_chars = CLAY_TB_BORDER_CHARS_NONE; } } const char *env_image_mode = getenv("CLAY_TB_IMAGE_MODE"); if (NULL != env_image_mode) { if (0 == strcmp("DEFAULT", env_image_mode)) { new_image_mode = CLAY_TB_IMAGE_MODE_DEFAULT; } else if (0 == strcmp("PLACEHOLDER", env_image_mode)) { new_image_mode = CLAY_TB_IMAGE_MODE_PLACEHOLDER; } else if (0 == strcmp("BG", env_image_mode)) { new_image_mode = CLAY_TB_IMAGE_MODE_BG; } else if (0 == strcmp("ASCII_FG", env_image_mode)) { new_image_mode = CLAY_TB_IMAGE_MODE_ASCII_FG; } else if (0 == strcmp("ASCII", env_image_mode)) { new_image_mode = CLAY_TB_IMAGE_MODE_ASCII; } else if (0 == strcmp("UNICODE", env_image_mode)) { new_image_mode = CLAY_TB_IMAGE_MODE_UNICODE; } else if (0 == strcmp("ASCII_FG_FAST", env_image_mode)) { new_image_mode = CLAY_TB_IMAGE_MODE_ASCII_FG_FAST; } else if (0 == strcmp("ASCII_FAST", env_image_mode)) { new_image_mode = CLAY_TB_IMAGE_MODE_ASCII_FAST; } else if (0 == strcmp("UNICODE_FAST", env_image_mode)) { new_image_mode = CLAY_TB_IMAGE_MODE_UNICODE_FAST; } } const char *env_transparency = getenv("CLAY_TB_TRANSPARENCY"); if (NULL != env_transparency) { if (0 == strcmp("1", env_transparency)) { new_transparency = true; } else if (0 == strcmp("0", env_transparency)) { new_transparency = false; } } const char *env_cell_pixels = getenv("CLAY_TB_CELL_PIXELS"); if (NULL != env_cell_pixels) { const char *str_width = env_cell_pixels; const char *str_height = strstr(env_cell_pixels, "x") + 1; if (NULL + 1 != str_height) { bool missing_value = false; errno = 0; float cell_width = strtof(str_width, NULL); if (0 != errno || 0 > cell_width) { missing_value = true; } float cell_height = strtof(str_height, NULL); if (0 != errno || 0 >= cell_height) { missing_value = true; } if (!missing_value) { new_pixel_size = (clay_tb_pixel_dimensions) { cell_width, cell_height }; } } } // NO_COLOR indicates that ANSI colors shouldn't be used: https://no-color.org/ const char *env_nocolor = getenv("NO_COLOR"); if (NULL != env_nocolor && '\0' != env_nocolor[0]) { new_color_mode = CLAY_TB_OUTPUT_NOCOLOR; } tb_init(); tb_set_input_mode(TB_INPUT_MOUSE); // Enable mouse hover support // - see https://github.com/termbox/termbox2/issues/71#issuecomment-2179581609 // - 1003 "Any-event tracking" mode // - 1006 SGR extended coordinates (already enabled with TB_INPUT_MOUSE) tb_sendf("\x1b[?%d;%dh", 1003, 1006); clay_tb_initialized = true; Clay_Termbox_Set_Color_Mode(new_color_mode); Clay_Termbox_Set_Border_Mode(new_border_mode); Clay_Termbox_Set_Border_Chars(new_border_chars); Clay_Termbox_Set_Image_Mode(new_image_mode); Clay_Termbox_Set_Transparency(new_transparency); Clay_Termbox_Set_Cell_Pixel_Size(new_pixel_size.width, new_pixel_size.height); size_t size = (size_t)tb_width() * tb_height(); clay_tb_color_buffer_clay = tb_malloc(sizeof(Clay_Color) * size); for (int i = 0; i < size; ++i) { clay_tb_color_buffer_clay[i] = (Clay_Color) { 0, 0, 0, 0 }; } } void Clay_Termbox_Close(void) { if (clay_tb_initialized) { // Disable mouse hover support tb_sendf("\x1b[?%d;%dl", 1003, 1006); tb_free(clay_tb_color_buffer_clay); tb_shutdown(); clay_tb_initialized = false; } } void Clay_Termbox_Render(Clay_RenderCommandArray commands) { clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); clay_tb_resize_buffer(); clay_tb_partial_image_drawn = false; clay_tb_image_fuel_used = 0; for (int32_t i = 0; i < commands.length; ++i) { const Clay_RenderCommand *command = Clay_RenderCommandArray_Get(&commands, i); const clay_tb_cell_bounding_box cell_box = cell_snap_bounding_box(command->boundingBox); int box_begin_x = CLAY__MAX(cell_box.x, 0); int box_end_x = CLAY__MIN(cell_box.x + cell_box.width, tb_width()); int box_begin_y = CLAY__MAX(cell_box.y, 0); int box_end_y = CLAY__MIN(cell_box.y + cell_box.height, tb_height()); if (box_end_x < 0 || box_end_y < 0 || tb_width() < box_begin_x || tb_height() < box_begin_y) { continue; } switch (command->commandType) { default: { clay_tb_assert(false, "Unhandled command: %d\n", command->commandType); } case CLAY_RENDER_COMMAND_TYPE_NONE: { break; } case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData render_data = command->renderData.rectangle; Clay_Color color_fg = { 0, 0, 0, 0 }; Clay_Color color_bg = render_data.backgroundColor; uintattr_t color_tb_fg = TB_DEFAULT; uintattr_t color_tb_bg = clay_tb_color_convert(color_bg); for (int y = box_begin_y; y < box_end_y; ++y) { for (int x = box_begin_x; x < box_end_x; ++x) { clay_tb_color_pair color_bg_new = clay_tb_get_transparency_color( x, y, (clay_tb_color_pair) { color_bg, color_tb_bg }); clay_tb_set_cell( x, y, ' ', color_tb_fg, color_bg_new.termbox, color_bg_new.clay); } } break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { if (CLAY_TB_BORDER_CHARS_NONE == clay_tb_border_chars) { break; } Clay_BorderRenderData render_data = command->renderData.border; Clay_Color color_fg = { 0, 0, 0, 1 }; Clay_Color color_bg = render_data.color; uintattr_t color_tb_fg = TB_DEFAULT; uintattr_t color_tb_bg = clay_tb_color_convert(color_bg); int border_skip_begin_x = box_begin_x; int border_skip_end_x = box_end_x; int border_skip_begin_y = box_begin_y; int border_skip_end_y = box_end_y; switch (clay_tb_border_mode) { default: { clay_tb_assert(false, "Invalid or unimplemented border mode (%d)", clay_tb_border_mode); break; } case CLAY_TB_BORDER_MODE_MINIMUM: { // Borders will be at least one cell wide if width is nonzero // and the bounding box is large enough to not be all borders if (0 < cell_box.width) { if (0 < render_data.width.left) { border_skip_begin_x = box_begin_x + (int)CLAY__MAX( 1, (render_data.width.left / clay_tb_cell_size.width)); } if (0 < render_data.width.right) { border_skip_end_x = box_end_x - (int)CLAY__MAX( 1, (render_data.width.right / clay_tb_cell_size.width)); } } if (0 < cell_box.height) { if (0 < render_data.width.top) { border_skip_begin_y = box_begin_y + (int)CLAY__MAX( 1, (render_data.width.top / clay_tb_cell_size.width)); } if (0 < render_data.width.bottom) { border_skip_end_y = box_end_y - (int)CLAY__MAX( 1, (render_data.width.bottom / clay_tb_cell_size.width)); } } break; } case CLAY_TB_BORDER_MODE_ROUND: { int halfwidth = clay_tb_roundf(clay_tb_cell_size.width / 2); int halfheight = clay_tb_roundf(clay_tb_cell_size.height / 2); if (halfwidth < render_data.width.left) { border_skip_begin_x = box_begin_x + (int)CLAY__MAX( 1, (render_data.width.left / clay_tb_cell_size.width)); } if (halfwidth < render_data.width.right) { border_skip_end_x = box_end_x - (int)CLAY__MAX( 1, (render_data.width.right / clay_tb_cell_size.width)); } if (halfheight < render_data.width.top) { border_skip_begin_y = box_begin_y + (int)CLAY__MAX( 1, (render_data.width.top / clay_tb_cell_size.width)); } if (halfheight < render_data.width.bottom) { border_skip_end_y = box_end_y - (int)CLAY__MAX( 1, (render_data.width.bottom / clay_tb_cell_size.width)); } break; } } // Draw border, skipping over the center of the bounding box for (int y = box_begin_y; y < box_end_y; ++y) { for (int x = box_begin_x; x < box_end_x; ++x) { if ((border_skip_begin_x <= x && x < border_skip_end_x) && (border_skip_begin_y <= y && y < border_skip_end_y)) { x = border_skip_end_x - 1; continue; } uint32_t ch; switch (clay_tb_border_chars) { default: { clay_tb_assert(false, "Invalid or unimplemented border character mode (%d)", clay_tb_border_chars); } case CLAY_TB_BORDER_CHARS_UNICODE: { if ((x < border_skip_begin_x) && (y < border_skip_begin_y)) { // Top left ch = U'\u250c'; } else if ((x >= border_skip_end_x) && (y < border_skip_begin_y)) { // Top right ch = U'\u2510'; } else if ((x < border_skip_begin_x) && (y >= border_skip_end_y)) { // Bottom left ch = U'\u2514'; } else if ((x >= border_skip_end_x) && (y >= border_skip_end_y)) { // Bottom right ch = U'\u2518'; } else if (x < border_skip_begin_x || x >= border_skip_end_x) { ch = U'\u2502'; } else if (y < border_skip_begin_y || y >= border_skip_end_y) { ch = U'\u2500'; } break; } case CLAY_TB_BORDER_CHARS_DEFAULT: case CLAY_TB_BORDER_CHARS_ASCII: { if ((x < border_skip_begin_x || x >= border_skip_end_x) && (y < border_skip_begin_y || y >= border_skip_end_y)) { ch = '+'; } else if (x < border_skip_begin_x || x >= border_skip_end_x) { ch = '|'; } else if (y < border_skip_begin_y || y >= border_skip_end_y) { ch = '-'; } break; } case CLAY_TB_BORDER_CHARS_BLANK: { ch = ' '; break; } } clay_tb_color_pair color_bg_new = clay_tb_get_transparency_color( x, y, (clay_tb_color_pair) { color_bg, color_tb_bg }); clay_tb_set_cell( x, y, ch, color_tb_fg, color_bg_new.termbox, color_bg_new.clay); } } break; } case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData render_data = command->renderData.text; Clay_Color color_fg = render_data.textColor; uintattr_t color_tb_fg = clay_tb_color_convert(color_fg); Clay_StringSlice *text = &render_data.stringContents; int32_t i = 0; // culling text characters that are outside of the layout int h_clip = 0 - cell_box.x; while(h_clip > 0 && i < text->length){ uint32_t ch = ' '; int codepoint_length = tb_utf8_char_to_unicode(&ch, text->chars + i); if (0 > codepoint_length) { clay_tb_assert(false, "Invalid utf8"); } i += codepoint_length; h_clip -= 1; } // printing the rest of the characters for (int y = box_begin_y; y < box_end_y; ++y) { for (int x = box_begin_x; x < box_end_x;) { uint32_t ch = ' '; if (i < text->length) { int codepoint_length = tb_utf8_char_to_unicode(&ch, text->chars + i); if (0 > codepoint_length) { clay_tb_assert(false, "Invalid utf8"); } i += codepoint_length; uintattr_t color_tb_bg = (clay_tb_transparency) ? TB_DEFAULT : clay_tb_color_convert(clay_tb_color_buffer_clay_get(x, y)); Clay_Color color_bg = { 0 }; clay_tb_color_pair color_bg_new = clay_tb_get_transparency_color( x, y, (clay_tb_color_pair) { color_bg, color_tb_bg }); clay_tb_set_cell( x, y, ch, color_tb_fg, color_bg_new.termbox, color_bg_new.clay); } int codepoint_width = tb_wcwidth(ch); if (-1 == codepoint_width) { // Nonprintable character, use REPLACEMENT CHARACTER (U+FFFD) ch = U'\ufffd'; codepoint_width = tb_wcwidth(ch); } x += codepoint_width; } } break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { Clay_ImageRenderData render_data = command->renderData.image; Clay_Color color_fg = { 0, 0, 0, 0 }; Clay_Color color_bg = render_data.backgroundColor; uintattr_t color_tb_fg = clay_tb_color_convert(color_fg); uintattr_t color_tb_bg; // Only set background to the provided color if it's non-default bool color_specified = !(color_bg.r == 0 && color_bg.g == 0 && color_bg.b == 0 && color_bg.a == 0); if (color_specified) { color_tb_bg = clay_tb_color_convert(color_bg); } bool use_placeholder = true; clay_tb_image *image = (clay_tb_image *)render_data.imageData; if (!(CLAY_TB_IMAGE_MODE_PLACEHOLDER == clay_tb_image_mode || CLAY_TB_OUTPUT_NOCOLOR == clay_tb_color_mode)) { bool convert_success = (NULL != image) ? clay_tb_image_convert(image, cell_box.width, cell_box.height) : false; if (convert_success) { use_placeholder = false; } } if (!use_placeholder) { // Render image for (int y = box_begin_y; y < box_end_y; ++y) { int y_offset = y - cell_box.y; for (int x = box_begin_x; x < box_end_x; ++x) { int x_offset = x - cell_box.x; // Fetch cells from the image's cache if (!color_specified) { if (CLAY_TB_IMAGE_MODE_ASCII_FG == clay_tb_image_mode || CLAY_TB_IMAGE_MODE_ASCII_FG_FAST == clay_tb_image_mode) { color_bg = (Clay_Color) { 0, 0, 0, 0 }; color_tb_bg = TB_DEFAULT; } else { color_bg = image->internal .background[y_offset * cell_box.width + x_offset]; color_tb_bg = clay_tb_color_convert(color_bg); } } color_tb_fg = clay_tb_color_convert( image->internal.foreground[y_offset * cell_box.width + x_offset]); uint32_t ch = image->internal.characters[y_offset * cell_box.width + x_offset]; if (CLAY_TB_IMAGE_MODE_BG == clay_tb_image_mode) { ch = ' '; } clay_tb_set_cell(x, y, ch, color_tb_fg, color_tb_bg, color_bg); } } } else { // Render a placeholder pattern const char *placeholder_text = "[Image]"; int i = 0; unsigned long len = strlen(placeholder_text); for (int y = box_begin_y; y < box_end_y; ++y) { float percent_y = (float)(y - box_begin_y) / (float)cell_box.height; for (int x = box_begin_x; x < box_end_x; ++x) { char ch = ' '; if (i < len) { ch = placeholder_text[i++]; } if (!color_specified) { // Use a placeholder pattern for the image float percent_x = (float)(cell_box.width - (x - box_begin_x)) / (float)cell_box.width; if (percent_x > percent_y) { color_bg = (Clay_Color) { 0x94, 0xb4, 0xff, 0xff }; color_tb_bg = clay_tb_color_convert(color_bg); } else { color_bg = (Clay_Color) { 0x3f, 0xcc, 0x45, 0xff }; color_tb_bg = clay_tb_color_convert(color_bg); } } clay_tb_set_cell(x, y, ch, color_tb_fg, color_tb_bg, color_bg); } } } break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { clay_tb_scissor_box = (clay_tb_cell_bounding_box) { .x = box_begin_x, .y = box_begin_y, .width = box_end_x - box_begin_x, .height = box_end_y - box_begin_y, }; clay_tb_scissor_enabled = true; break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { clay_tb_scissor_enabled = false; break; } case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { break; } } } } void Clay_Termbox_Waitfor_Event(void) { if (clay_tb_partial_image_drawn) { return; } int termbox_ttyfd, termbox_resizefd; tb_get_fds(&termbox_ttyfd, &termbox_resizefd); int nfds = CLAY__MAX(termbox_ttyfd, termbox_resizefd) + 1; fd_set monitor_set; FD_ZERO(&monitor_set); FD_SET(termbox_ttyfd, &monitor_set); FD_SET(termbox_resizefd, &monitor_set); select(nfds, &monitor_set, NULL, NULL, NULL); } ================================================ FILE: renderers/termbox2/image_character_masks.h ================================================ #ifndef CLAY_TB_IMAGE_H #define CLAY_TB_IMAGE_H #include typedef struct { uint32_t character; int data[6 * 12]; } clay_tb_character_mask; // Ascii bitmap data from the Terminus 4.49 font (https://terminus-font.sourceforge.net/) // Licensed under the SIL Open Font License, Version 1.1 (https://scripts.sil.org/OFL) #define CLAY_TB_IMAGE_SHAPES_ASCII_BEST_COUNT 95 const clay_tb_character_mask clay_tb_image_shapes_ascii_best[CLAY_TB_IMAGE_SHAPES_ASCII_BEST_COUNT] = { { .character = ' ', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '!', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '"', .data = { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '#', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '$', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '%', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '&', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '\'', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '(', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = ')', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '*', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '+', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = ',', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '-', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '.', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '/', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '0', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '1', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '2', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '3', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '4', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '5', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '6', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '7', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '8', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '9', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = ':', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = ';', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '<', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '=', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '>', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '?', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '@', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'A', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'B', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'C', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'D', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'E', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'F', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'G', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'H', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'I', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'J', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'K', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'L', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'M', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'N', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'O', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'P', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'Q', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'R', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'S', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'T', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'U', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'V', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'W', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'X', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'Y', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'Z', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '[', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '\\', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = ']', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '^', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '_', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '`', .data = { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'a', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'b', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'c', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'd', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'e', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'f', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'g', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0 }, }, { .character = 'h', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'i', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'j', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, }, { .character = 'k', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'l', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'm', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'n', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'o', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'p', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, }, { .character = 'q', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0 }, }, { .character = 'r', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 's', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 't', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'u', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'v', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'w', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'x', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'y', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0 }, }, { .character = 'z', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '{', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '|', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '}', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '~', .data = { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, }; #define CLAY_TB_IMAGE_SHAPES_ASCII_FAST_COUNT 15 const clay_tb_character_mask clay_tb_image_shapes_ascii_fast[CLAY_TB_IMAGE_SHAPES_ASCII_FAST_COUNT] = { { .character = ' ', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '"', .data = { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '#', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '-', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '.', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '@', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'B', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'F', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'L', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '_', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = '`', .data = { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'g', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0 }, }, { .character = 'r', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = 'y', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0 }, }, { .character = '~', .data = { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, }; #define CLAY_TB_IMAGE_SHAPES_UNICODE_BEST_COUNT 52 const clay_tb_character_mask clay_tb_image_shapes_unicode_best[CLAY_TB_IMAGE_SHAPES_UNICODE_BEST_COUNT] = { { .character = ' ', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u2580', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u2581', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2582', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2583', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2584', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2585', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2586', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2587', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2588', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2589', .data = { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0 }, }, { .character = U'\u258a', .data = { 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0 }, }, { .character = U'\u258c', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, { .character = U'\u258e', .data = { 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, }, { .character = U'\u258f', .data = { 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, }, { .character = U'\u2590', .data = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1 }, }, { .character = U'\u2594', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u2595', .data = { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 }, }, { .character = U'\u2596', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, { .character = U'\u2597', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1 }, }, { .character = U'\u2598', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u2599', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u259a', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1 }, }, { .character = U'\u259b', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, { .character = U'\u259c', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1 }, }, { .character = U'\u259d', .data = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u259e', .data = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, { .character = U'\u259f', .data = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, // TODO: END HERE { .character = U'\u25e2', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u25e3', .data = { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u25e4', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u25e5', .data = { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb3c', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, { .character = U'\U0001fb3d', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\U0001fb3e', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, { .character = U'\U0001fb3f', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\U0001fb40', .data = { 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, { .character = U'\U0001fb47', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1 }, }, { .character = U'\U0001fb48', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\U0001fb49', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1 }, }, { .character = U'\U0001fb4a', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\U0001fb4b', .data = { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1 }, }, { .character = U'\U0001fb57', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb58', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb59', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb5a', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb59', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb62', .data = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb63', .data = { 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb64', .data = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb65', .data = { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\U0001fb66', .data = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 }, }, }; #define CLAY_TB_IMAGE_SHAPES_UNICODE_FAST_COUNT 15 const clay_tb_character_mask clay_tb_image_shapes_unicode_fast[CLAY_TB_IMAGE_SHAPES_UNICODE_FAST_COUNT] = { { .character = ' ', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u2580', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u2581', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2586', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2587', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, { .character = U'\u2589', .data = { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0 }, }, { .character = U'\u258c', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, { .character = U'\u258f', .data = { 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, }, { .character = U'\u2594', .data = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u2596', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, { .character = U'\u2597', .data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1 }, }, { .character = U'\u2598', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u259a', .data = { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1 }, }, { .character = U'\u259d', .data = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }, { .character = U'\u259e', .data = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, }, }; #endif // CLAY_TB_IMAGE_H ================================================ FILE: renderers/terminal/clay_renderer_terminal_ansi.c ================================================ #include "stdint.h" #include "string.h" #include "stdio.h" #include "stdlib.h" #ifdef CLAY_OVERFLOW_TRAP #include "signal.h" #endif static inline void Console_MoveCursor(int x, int y) { printf("\033[%d;%dH", y + 1, x + 1); } bool Clay_PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { // TODO this function is a copy of Clay__PointIsInsideRect but that one is internal, I don't know if we want // TODO to expose Clay__PointIsInsideRect return point.x >= rect.x && point.x < rect.x + rect.width && point.y >= rect.y && point.y < rect.y + rect.height; } static inline void Console_DrawRectangle(int x0, int y0, int width, int height, Clay_Color color, Clay_BoundingBox scissorBox) { float average = (color.r + color.g + color.b + color.a) / 4 / 255; for (int y = y0; y < height + y0; y++) { for (int x = x0; x < width + x0; x++) { if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = x, .y = y}, scissorBox)) { continue; } Console_MoveCursor(x, y); // TODO this should be replaced by a better logarithmic scale if we're doing black and white if (average > 0.75) { printf("█"); } else if (average > 0.5) { printf("▓"); } else if (average > 0.25) { printf("▒"); } else { printf("░"); } } } } static inline Clay_Dimensions Console_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { Clay_Dimensions textSize = {0}; int columnWidth = *(int *) userData; // TODO this function is very wrong, it measures in characters, I have no idea what is the size in pixels float maxTextWidth = 0.0f; float lineTextWidth = 0; float textHeight = 1; for (int i = 0; i < text.length; ++i) { if (text.chars[i] == '\n') { maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; lineTextWidth = 0; textHeight++; continue; } lineTextWidth++; } maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; textSize.width = maxTextWidth * columnWidth; textSize.height = textHeight * columnWidth; return textSize; } void Clay_Terminal_Render(Clay_RenderCommandArray renderCommands, int width, int height, int columnWidth) { printf("\033[H\033[J"); // Clear const Clay_BoundingBox fullWindow = { .x = 0, .y = 0, .width = (float) width, .height = (float) height, }; Clay_BoundingBox scissorBox = fullWindow; for (int j = 0; j < renderCommands.length; j++) { Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); Clay_BoundingBox boundingBox = (Clay_BoundingBox) { .x = (int)((renderCommand->boundingBox.x / columnWidth) + 0.5), .y = (int)((renderCommand->boundingBox.y / columnWidth) + 0.5), .width = (int)((renderCommand->boundingBox.width / columnWidth) + 0.5), .height = (int)((renderCommand->boundingBox.height / columnWidth) + 0.5), }; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData data = renderCommand->renderData.text; Clay_StringSlice text = data.stringContents; int y = 0; for (int x = 0; x < text.length; x++) { if (text.chars[x] == '\n') { y++; continue; } int cursorX = (int) boundingBox.x + x; int cursorY = (int) boundingBox.y + y; if (cursorY > scissorBox.y + scissorBox.height) { break; } if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = cursorX, .y = cursorY}, scissorBox)) { continue; } Console_MoveCursor(cursorX, cursorY); printf("%c", text.chars[x]); } break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { scissorBox = boundingBox; break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { scissorBox = fullWindow; break; } case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData data = renderCommand->renderData.rectangle; Console_DrawRectangle( (int) boundingBox.x, (int) boundingBox.y, (int) boundingBox.width, (int) boundingBox.height, data.backgroundColor, scissorBox); break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData data = renderCommand->renderData.border; // Left border if (data.width.left > 0) { Console_DrawRectangle( (int) (boundingBox.x), (int) (boundingBox.y + data.cornerRadius.topLeft), (int) data.width.left, (int) (boundingBox.height - data.cornerRadius.topLeft - data.cornerRadius.bottomLeft), data.color, scissorBox); } // Right border if (data.width.right > 0) { Console_DrawRectangle( (int) (boundingBox.x + boundingBox.width - data.width.right), (int) (boundingBox.y + data.cornerRadius.topRight), (int) data.width.right, (int) (boundingBox.height - data.cornerRadius.topRight - data.cornerRadius.bottomRight), data.color, scissorBox); } // Top border if (data.width.top > 0) { Console_DrawRectangle( (int) (boundingBox.x + data.cornerRadius.topLeft), (int) (boundingBox.y), (int) (boundingBox.width - data.cornerRadius.topLeft - data.cornerRadius.topRight), (int) data.width.top, data.color, scissorBox); } // Bottom border if (data.width.bottom > 0) { Console_DrawRectangle( (int) (boundingBox.x + data.cornerRadius.bottomLeft), (int) (boundingBox.y + boundingBox.height - data.width.bottom), (int) (boundingBox.width - data.cornerRadius.bottomLeft - data.cornerRadius.bottomRight), (int) data.width.bottom, data.color, scissorBox); } break; } default: { printf("Error: unhandled render command."); #ifdef CLAY_OVERFLOW_TRAP raise(SIGTRAP); #endif exit(1); } } } Console_MoveCursor(-1, -1); // TODO make the user not be able to write } ================================================ FILE: renderers/web/build-wasm.sh ================================================ cp ../../clay.h clay.c && \ clang \ -Os \ -DCLAY_WASM \ -mbulk-memory \ --target=wasm32 \ -nostdlib \ -Wl,--strip-all \ -Wl,--export-dynamic \ -Wl,--no-entry \ -Wl,--export=__heap_base \ -Wl,--initial-memory=6553600 \ -o clay.wasm clay.c; rm clay.c; ================================================ FILE: renderers/web/canvas2d/clay-canvas2d-renderer.html ================================================ Clay - UI Layout Library ================================================ FILE: renderers/web/html/clay-html-renderer.html ================================================ Clay - UI Layout Library ================================================ FILE: renderers/win32_gdi/README.md ================================================ The windows GDI renderer example is missing the following: - Images - Rendering Rounded Rectangle borders - Custom Fonts (font size) ================================================ FILE: renderers/win32_gdi/clay_renderer_gdi.c ================================================ #include #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) #include // AVX intrinsincs for faster sqrtf #endif #include "../../clay.h" HDC renderer_hdcMem = {0}; HBITMAP renderer_hbmMem = {0}; HANDLE renderer_hOld = {0}; DWORD g_dwGdiRenderFlags; #ifndef RECTWIDTH #define RECTWIDTH(rc) ((rc).right - (rc).left) #endif #ifndef RECTHEIGHT #define RECTHEIGHT(rc) ((rc).bottom - (rc).top) #endif // Renderer options bit flags // RF clearly stated in the name to avoid confusion with possible macro definitions for other purposes #define CLAYGDI_RF_ALPHABLEND 0x00000001 #define CLAYGDI_RF_SMOOTHCORNERS 0x00000002 // These are bitflags, not indexes. Next would be 0x00000004 inline DWORD Clay_Win32_GetRendererFlags() { return g_dwGdiRenderFlags; } // Replaces the rendering flags with new ones provided inline void Clay_Win32_SetRendererFlags(DWORD dwFlags) { g_dwGdiRenderFlags = dwFlags; } // Returns `true` if flags were modified inline bool Clay_Win32_ModifyRendererFlags(DWORD dwRemove, DWORD dwAdd) { DWORD dwSavedFlags = g_dwGdiRenderFlags; DWORD dwNewFlags = (dwSavedFlags & ~dwRemove) | dwAdd; if (dwSavedFlags == dwNewFlags) return false; Clay_Win32_SetRendererFlags(dwNewFlags); return true; } /*----------------------------------------------------------------------------+ | Math stuff start | +----------------------------------------------------------------------------*/ // Intrinsincs wrappers #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) inline float intrin_sqrtf(const float f) { __m128 temp = _mm_set_ss(f); temp = _mm_sqrt_ss(temp); return _mm_cvtss_f32(temp); } #endif // Use fast inverse square root #if defined(USE_FAST_SQRT) float fast_inv_sqrtf(float number) { const float threehalfs = 1.5f; float x2 = number * 0.5f; float y = number; // Evil bit-level hacking uint32_t i = *(uint32_t*)&y; i = 0x5f3759df - (i >> 1); // Initial guess for Newton's method y = *(float*)&i; // One iteration of Newton's method y = y * (threehalfs - (x2 * y * y)); // y = y * (1.5 - 0.5 * x * y^2) return y; } // Fast square root approximation using the inverse square root float fast_sqrtf(float number) { if (number < 0.0f) return 0.0f; // Handle negative input return number * fast_inv_sqrtf(number); } #endif // sqrtf_impl implementation chooser #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) #define sqrtf_impl(x) intrin_sqrtf(x) #elif defined(USE_FAST_SQRT) #define sqrtf_impl(x) fast_sqrtf(x) #else #define sqrtf_impl(x) sqrtf(x) // Fallback to std sqrtf #endif /*----------------------------------------------------------------------------+ | Math stuff end | +----------------------------------------------------------------------------*/ static inline Clay_Color ColorBlend(Clay_Color base, Clay_Color overlay, float factor) { Clay_Color blended; // Normalize alpha values for multiplications float base_a = base.a / 255.0f; float overlay_a = overlay.a / 255.0f; overlay_a *= factor; float out_a = overlay_a + base_a * (1.0f - overlay_a); // Avoid division by zero and fully transparent cases if (out_a <= 0.0f) { return (Clay_Color) { .a = 0, .r = 0, .g = 0, .b = 0 }; } blended.r = (overlay.r * overlay_a + base.r * base_a * (1.0f - overlay_a)) / out_a; blended.g = (overlay.g * overlay_a + base.g * base_a * (1.0f - overlay_a)) / out_a; blended.b = (overlay.b * overlay_a + base.b * base_a * (1.0f - overlay_a)) / out_a; blended.a = out_a * 255.0f; // Denormalize alpha back return blended; } static float RoundedRectPixelCoverage(int x, int y, const Clay_CornerRadius radius, int width, int height) { // Check if the pixel is in one of the four rounded corners if (x < radius.topLeft && y < radius.topLeft) { // Top-left corner float dx = radius.topLeft - x - 1; float dy = radius.topLeft - y - 1; float distance = sqrtf_impl(dx * dx + dy * dy); if (distance > radius.topLeft) return 0.0f; if (distance <= radius.topLeft - 1) return 1.0f; return radius.topLeft - distance; } else if (x >= width - radius.topRight && y < radius.topRight) { // Top-right corner float dx = x - (width - radius.topRight); float dy = radius.topRight - y - 1; float distance = sqrtf_impl(dx * dx + dy * dy); if (distance > radius.topRight) return 0.0f; if (distance <= radius.topRight - 1) return 1.0f; return radius.topRight - distance; } else if (x < radius.bottomLeft && y >= height - radius.bottomLeft) { // Bottom-left corner float dx = radius.bottomLeft - x - 1; float dy = y - (height - radius.bottomLeft); float distance = sqrtf_impl(dx * dx + dy * dy); if (distance > radius.bottomLeft) return 0.0f; if (distance <= radius.bottomLeft - 1) return 1.0f; return radius.bottomLeft - distance; } else if (x >= width - radius.bottomRight && y >= height - radius.bottomRight) { // Bottom-right corner float dx = x - (width - radius.bottomRight); float dy = y - (height - radius.bottomRight); float distance = sqrtf_impl(dx * dx + dy * dy); if (distance > radius.bottomRight) return 0.0f; if (distance <= radius.bottomRight - 1) return 1.0f; return radius.bottomRight - distance; } else { // Not in a corner, full coverage return 1.0f; } } typedef struct { HDC hdcMem; HBITMAP hbmMem; HBITMAP hbmMemPrev; void* pBits; SIZE size; } HDCSubstitute; static void CreateHDCSubstitute(HDCSubstitute* phdcs, HDC hdcSrc, PRECT prc) { if (prc == NULL) return; phdcs->size = (SIZE){ RECTWIDTH(*prc), RECTHEIGHT(*prc) }; if (phdcs->size.cx <= 0 || phdcs->size.cy <= 0) return; phdcs->hdcMem = CreateCompatibleDC(hdcSrc); if (phdcs->hdcMem == NULL) return; // Create a 32-bit DIB section for the memory DC BITMAPINFO bmi = { 0 }; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = phdcs->size.cx; bmi.bmiHeader.biHeight = -phdcs->size.cy; // I think it's faster? Probably bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; phdcs->pBits = NULL; phdcs->hbmMem = CreateDIBSection(phdcs->hdcMem, &bmi, DIB_RGB_COLORS, &phdcs->pBits, NULL, 0); if (phdcs->hbmMem == NULL) { DeleteDC(phdcs->hdcMem); return; } // Select the DIB section into the memory DC phdcs->hbmMemPrev = SelectObject(phdcs->hdcMem, phdcs->hbmMem); // Copy the content of the target DC to the memory DC BitBlt(phdcs->hdcMem, 0, 0, phdcs->size.cx, phdcs->size.cy, hdcSrc, prc->left, prc->top, SRCCOPY); } static void DestroyHDCSubstitute(HDCSubstitute* phdcs) { if (phdcs == NULL) return; // Clean up SelectObject(phdcs->hdcMem, phdcs->hbmMemPrev); DeleteObject(phdcs->hbmMem); DeleteDC(phdcs->hdcMem); ZeroMemory(phdcs, sizeof(HDCSubstitute)); } static void __Clay_Win32_FillRoundRect(HDC hdc, PRECT prc, Clay_Color color, Clay_CornerRadius radius) { HDCSubstitute substitute = { 0 }; CreateHDCSubstitute(&substitute, hdc, prc); bool has_corner_radius = radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight; if (has_corner_radius) { // Limit the corner radius to the minimum of half the width and half the height float max_radius = (float)fmin(substitute.size.cx / 2.0f, substitute.size.cy / 2.0f); if (radius.topLeft > max_radius) radius.topLeft = max_radius; if (radius.topRight > max_radius) radius.topRight = max_radius; if (radius.bottomLeft > max_radius) radius.bottomLeft = max_radius; if (radius.bottomRight > max_radius) radius.bottomRight = max_radius; } // Iterate over each pixel in the DIB section uint32_t* pixels = (uint32_t*)substitute.pBits; for (int y = 0; y < substitute.size.cy; ++y) { for (int x = 0; x < substitute.size.cx; ++x) { float coverage = 1.0f; if (has_corner_radius) coverage = RoundedRectPixelCoverage(x, y, radius, substitute.size.cx, substitute.size.cy); if (coverage > 0.0f) { uint32_t pixel = pixels[y * substitute.size.cx + x]; Clay_Color dst_color = { .r = (float)((pixel >> 16) & 0xFF), // Red .g = (float)((pixel >> 8) & 0xFF), // Green .b = (float)(pixel & 0xFF), // Blue .a = 255.0f // Fully opaque }; Clay_Color blended = ColorBlend(dst_color, color, coverage); pixels[y * substitute.size.cx + x] = ((uint32_t)(blended.b) << 0) | ((uint32_t)(blended.g) << 8) | ((uint32_t)(blended.r) << 16); } } } // Copy the blended content back to the target DC BitBlt(hdc, prc->left, prc->top, substitute.size.cx, substitute.size.cy, substitute.hdcMem, 0, 0, SRCCOPY); DestroyHDCSubstitute(&substitute); } void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands, HFONT* fonts) { bool is_clipping = false; HRGN clipping_region = {0}; PAINTSTRUCT ps; HDC hdc; RECT rc; // Top left of our window GetWindowRect(hwnd, &rc); hdc = BeginPaint(hwnd, &ps); int win_width = rc.right - rc.left, win_height = rc.bottom - rc.top; // Create an off-screen DC for double-buffering renderer_hdcMem = CreateCompatibleDC(hdc); renderer_hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height); renderer_hOld = SelectObject(renderer_hdcMem, renderer_hbmMem); // draw for (int j = 0; j < renderCommands.length; j++) { Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); Clay_BoundingBox boundingBox = renderCommand->boundingBox; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_Color c = renderCommand->renderData.text.textColor; SetTextColor(renderer_hdcMem, RGB(c.r, c.g, c.b)); SetBkMode(renderer_hdcMem, TRANSPARENT); RECT r = rc; r.left = boundingBox.x; r.top = boundingBox.y; r.right = boundingBox.x + boundingBox.width + r.right; r.bottom = boundingBox.y + boundingBox.height + r.bottom; uint16_t font_id = renderCommand->renderData.text.fontId; HFONT hFont = fonts[font_id]; HFONT hPrevFont = SelectObject(renderer_hdcMem, hFont); // Actually draw text DrawTextA(renderer_hdcMem, renderCommand->renderData.text.stringContents.chars, renderCommand->renderData.text.stringContents.length, &r, DT_TOP | DT_LEFT); SelectObject(renderer_hdcMem, hPrevFont); break; } case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { DWORD dwFlags = Clay_Win32_GetRendererFlags(); Clay_RectangleRenderData rrd = renderCommand->renderData.rectangle; RECT r = rc; r.left = boundingBox.x; r.top = boundingBox.y; r.right = boundingBox.x + boundingBox.width; r.bottom = boundingBox.y + boundingBox.height; bool translucid = false; // There is need to check that only if alphablending is enabled. // In other case the blending will be always opaque and we can jump to simpler FillRgn/Rect if (dwFlags & CLAYGDI_RF_ALPHABLEND) translucid = rrd.backgroundColor.a > 0.0f && rrd.backgroundColor.a < 255.0f; bool has_rounded_corners = rrd.cornerRadius.topLeft > 0.0f || rrd.cornerRadius.topRight > 0.0f || rrd.cornerRadius.bottomLeft > 0.0f || rrd.cornerRadius.bottomRight > 0.0f; // We go here if CLAYGDI_RF_SMOOTHCORNERS flag is set and one of the corners is rounded // Also we go here if GLAYGDI_RF_ALPHABLEND flag is set and the fill color is translucid if ((dwFlags & CLAYGDI_RF_ALPHABLEND) && translucid || (dwFlags & CLAYGDI_RF_SMOOTHCORNERS) && has_rounded_corners) { __Clay_Win32_FillRoundRect(renderer_hdcMem, &r, rrd.backgroundColor, rrd.cornerRadius); } else { HBRUSH recColor = CreateSolidBrush(RGB(rrd.backgroundColor.r, rrd.backgroundColor.g, rrd.backgroundColor.b)); if (has_rounded_corners) { HRGN roundedRectRgn = CreateRoundRectRgn( r.left, r.top, r.right + 1, r.bottom + 1, rrd.cornerRadius.topLeft * 2, rrd.cornerRadius.topLeft * 2); FillRgn(renderer_hdcMem, roundedRectRgn, recColor); DeleteObject(roundedRectRgn); } else { FillRect(renderer_hdcMem, &r, recColor); } DeleteObject(recColor); } break; } // The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox. case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { is_clipping = true; clipping_region = CreateRectRgn(boundingBox.x, boundingBox.y, boundingBox.x + boundingBox.width, boundingBox.y + boundingBox.height); SelectClipRgn(renderer_hdcMem, clipping_region); break; } // The renderer should finish any previously active clipping, and begin rendering elements in full again. case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { SelectClipRgn(renderer_hdcMem, NULL); if (clipping_region) { DeleteObject(clipping_region); } is_clipping = false; clipping_region = NULL; break; } // The renderer should draw a colored border inset into the bounding box. case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData brd = renderCommand->renderData.border; RECT r = rc; r.left = boundingBox.x; r.top = boundingBox.y; r.right = boundingBox.x + boundingBox.width; r.bottom = boundingBox.y + boundingBox.height; HPEN topPen = CreatePen(PS_SOLID, brd.width.top, RGB(brd.color.r, brd.color.g, brd.color.b)); HPEN leftPen = CreatePen(PS_SOLID, brd.width.left, RGB(brd.color.r, brd.color.g, brd.color.b)); HPEN bottomPen = CreatePen(PS_SOLID, brd.width.bottom, RGB(brd.color.r, brd.color.g, brd.color.b)); HPEN rightPen = CreatePen(PS_SOLID, brd.width.right, RGB(brd.color.r, brd.color.g, brd.color.b)); HPEN oldPen = SelectObject(renderer_hdcMem, topPen); if (brd.cornerRadius.topLeft == 0) { MoveToEx(renderer_hdcMem, r.left, r.top, NULL); LineTo(renderer_hdcMem, r.right, r.top); SelectObject(renderer_hdcMem, leftPen); MoveToEx(renderer_hdcMem, r.left, r.top, NULL); LineTo(renderer_hdcMem, r.left, r.bottom); SelectObject(renderer_hdcMem, bottomPen); MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL); LineTo(renderer_hdcMem, r.right, r.bottom); SelectObject(renderer_hdcMem, rightPen); MoveToEx(renderer_hdcMem, r.right, r.top, NULL); LineTo(renderer_hdcMem, r.right, r.bottom); } else { // todo: i should be rounded MoveToEx(renderer_hdcMem, r.left, r.top, NULL); LineTo(renderer_hdcMem, r.right, r.top); SelectObject(renderer_hdcMem, leftPen); MoveToEx(renderer_hdcMem, r.left, r.top, NULL); LineTo(renderer_hdcMem, r.left, r.bottom); SelectObject(renderer_hdcMem, bottomPen); MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL); LineTo(renderer_hdcMem, r.right, r.bottom); SelectObject(renderer_hdcMem, rightPen); MoveToEx(renderer_hdcMem, r.right, r.top, NULL); LineTo(renderer_hdcMem, r.right, r.bottom); } SelectObject(renderer_hdcMem, oldPen); DeleteObject(topPen); DeleteObject(leftPen); DeleteObject(bottomPen); DeleteObject(rightPen); break; } // case CLAY_RENDER_COMMAND_TYPE_IMAGE: // { // // TODO: i couldnt get the win 32 api to load a bitmap.... So im punting on this one :( // break; // } default: printf("Unhandled render command %d\r\n", renderCommand->commandType); break; } } BitBlt(hdc, 0, 0, win_width, win_height, renderer_hdcMem, 0, 0, SRCCOPY); // Free-up the off-screen DC SelectObject(renderer_hdcMem, renderer_hOld); DeleteObject(renderer_hbmMem); DeleteDC(renderer_hdcMem); EndPaint(hwnd, &ps); } /* Hacks due to the windows api not making sence to use.... may measure too large, but never too small */ #ifndef WIN32_FONT_HEIGHT #define WIN32_FONT_HEIGHT (16) #endif #ifndef WIN32_FONT_WIDTH #define WIN32_FONT_WIDTH (8) #endif static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { Clay_Dimensions textSize = {0}; if (userData != NULL) { HFONT* fonts = (HFONT*)userData; HFONT hFont = fonts[config->fontId]; if (hFont != NULL) { HDC hScreenDC = GetDC(NULL); HDC hTempDC = CreateCompatibleDC(hScreenDC); if (hTempDC != NULL) { HFONT hPrevFont = SelectObject(hTempDC, hFont); SIZE size; GetTextExtentPoint32(hTempDC, text.chars, text.length, &size); textSize.width = size.cx; textSize.height = size.cy; SelectObject(hScreenDC, hPrevFont); DeleteDC(hTempDC); return textSize; } ReleaseDC(HWND_DESKTOP, hScreenDC); } } // Fallback for system bitmap font float maxTextWidth = 0.0f; float lineTextWidth = 0; float textHeight = WIN32_FONT_HEIGHT; for (int i = 0; i < text.length; ++i) { if (text.chars[i] == '\n') { maxTextWidth = fmax(maxTextWidth, lineTextWidth); lineTextWidth = 0; continue; } lineTextWidth += WIN32_FONT_WIDTH; } maxTextWidth = fmax(maxTextWidth, lineTextWidth); textSize.width = maxTextWidth; textSize.height = textHeight; return textSize; } HFONT Clay_Win32_SimpleCreateFont(const char* filePath, const char* family, int height, int weight) { // Add the font resource to the application instance int fontAdded = AddFontResourceEx(filePath, FR_PRIVATE, NULL); if (fontAdded == 0) { return NULL; } int fontHeight = height; // If negative, treat height as Pt rather than pixels if (height < 0) { // Get the screen DPI HDC hScreenDC = GetDC(NULL); int iScreenDPI = GetDeviceCaps(hScreenDC, LOGPIXELSY); ReleaseDC(HWND_DESKTOP, hScreenDC); // Convert font height from points to pixels fontHeight = MulDiv(height, iScreenDPI, 72); } // Create the font using the calculated height and the font name HFONT hFont = CreateFont(fontHeight, 0, 0, 0, weight, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, family); return hFont; } ================================================ FILE: tests/docker-compose.yml ================================================ services: gcc: build: context: ../ dockerfile: tests/gcc/9.4/Dockerfile volumes: - /tmp/clay/_deps ================================================ FILE: tests/gcc/9.4/Dockerfile ================================================ FROM --platform=linux/amd64 ubuntu:20.04 RUN apt update -y RUN DEBIAN_FRONTEND=noninteractive apt install -y build-essential RUN DEBIAN_FRONTEND=noninteractive apt install -y wget WORKDIR /tmp/ RUN wget https://github.com/Kitware/CMake/releases/download/v3.28.4/cmake-3.28.4-linux-x86_64.tar.gz RUN tar zxvf cmake-3.28.4-linux-x86_64.tar.gz RUN DEBIAN_FRONTEND=noninteractive apt install -y git RUN DEBIAN_FRONTEND=noninteractive apt install -y libwayland-dev RUN DEBIAN_FRONTEND=noninteractive apt install -y pkg-config RUN DEBIAN_FRONTEND=noninteractive apt install -y libxkbcommon-dev RUN DEBIAN_FRONTEND=noninteractive apt install -y xorg-dev ADD . /tmp/clay WORKDIR /tmp/clay CMD /tmp/cmake-3.28.4-linux-x86_64/bin/cmake . && /tmp/cmake-3.28.4-linux-x86_64/bin/cmake --build . ================================================ FILE: tests/run-tests.sh ================================================ docker compose build && docker compose up && echo "Tests complete."