diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e49d685 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +cmake-build-debug/ +cmake-build-release/ +.DS_Store +.idea/ +build/ +node_modules/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..86955ec --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.28) +project(clay C) + +set(CMAKE_C_STANDARD 99) + +add_subdirectory("examples/raylib-sidebar-scrolling-container") +add_subdirectory("examples/clay-official-website") \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2968c61 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d2c071 --- /dev/null +++ b/README.md @@ -0,0 +1,1417 @@ +# Clay +**_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 ~2k 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). + +A screenshot of a code IDE with lots of visual and textual elements + +_An example GUI application built with clay_ + +## Quick Start + +1. Download or clone clay.h and include it. + +```C +#include "clay.h" +``` + +2. Ask clay for how much static memory it needs using [Clay_MinMemorySize()](#clay_minmemorysize), create an Arena for it to use with [Clay_CreateArenaWithCapacityAndMemory(size, void *memory)](#clay_createarenawithcapacityandmemory), and initialize it with [Clay_Initialize(arena)](#clay_initialize). + +```C +// 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)); +Clay_Initialize(arena); +``` + +3. Provide a `MeasureText(text, config)` function pointer with [Clay_SetMeasureTextFunction(function)](#clay_setmeasuretextfunction) so that clay can measure and wrap text. + +```C +// Example measure text function +static inline Clay_Dimensions MeasureText(Clay_String *text, Clay_TextElementConfig *config) { + // Clay_TextElementConfig contains members such as fontId, fontSize, letterSpacing etc + // Note: Clay_String->chars is not guaranteed to be null terminated +} + +// Tell clay how to measure text +Clay_SetMeasureTextFunction(MeasureText); +``` + +4. **Optional** - Call [Clay_SetPointerPosition(pointerPosition)](#clay_setpointerposition) if you want to use mouse interactions. + +```C +// Update internal pointer position for handling mouseover / click / touch events +Clay_SetPointerPosition((Clay_Vector2) { mousePositionX, mousePositionY }); +``` + +5. Call [Clay_BeginLayout(screenWidth, screenHeight)](#clay_beginlayout) and declare your layout using the provided macros. + +```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}; + +// Layout config is just a struct that can be declared statically, or inline +Clay_LayoutConfig sidebarItemLayout = (Clay_LayoutConfig) { + .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) }, +}; + +// Re-useable components are just normal functions +void SidebarItemComponent(int index) { + CLAY_RECTANGLE(CLAY_IDI("SidebarBlob", index), sidebarItemLayout, CLAY_RECTANGLE_CONFIG(.color = COLOR_ORANGE), {}); +} + +// An example function to begin the "root" of your layout tree +Clay_RenderCommandArray CreateLayout() { + Clay_BeginLayout(windowWidth, windowHeight); + + // An example of laying out a UI with a fixed width sidebar and flexible width main content + CLAY_RECTANGLE(CLAY_ID("OuterContainer"), CLAY_LAYOUT(.sizing = {CLAY_SIZING_GROW(), CLAY_SIZING_GROW()}, .padding = {16, 16}, .childGap = 16), CLAY_RECTANGLE_CONFIG(.color = {250,250,255,255}) { + CLAY_RECTANGLE(CLAY_ID("SideBar"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16}, .childGap = 16), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { + CLAY_RECTANGLE(CLAY_ID("ProfilePictureOuter"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }, .padding = {16, 16}, .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED), { + CLAY_IMAGE(CLAY_ID("ProfilePicture"), CLAY_LAYOUT( .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }), CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .height = 60, .width = 60), {}); + CLAY_TEXT(CLAY_ID("ProfileTitle"), 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(i); + } + }); + + CLAY_RECTANGLE(CLAY_ID("MainContent"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), {}); + }); + // ... +}); +``` + +6. Call [Clay_EndLayout(screenWidth, screenHeight)](#clay_endlayout) and process the resulting [Clay_RenderCommandArray](#clay_rendercommandarray) in your choice of renderer. + +```C +Clay_RenderCommandArray renderCommands = Clay_EndLayout(windowWidth, windowHeight); + +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->config.rectangleElementConfig->color); + } + // ... 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_SetPointerPosition(pointerPosition)](#clay_setpointerposition) +2. [Clay_UpdateScrollContainers(isPointerActive, scrollDelta, deltaTime)](#clay_updatescrollcontainers) +3. [Clay_BeginLayout(screenWidth, screenHeight)](#clay_beginlayout) +4. Declare your layout with the provided [Element Macros](#element-macros) +5. [Clay_EndLayout(screenWidth, screenHeight)](#clay_endlayout) +6. Render the results using the outputted [Clay_RenderCommandArray](#clay_rendercommandarray) + +## High Level Documentation + +### Building UI Hierarchies +Clay UI hierarchies are built using C macros that allow _nested_ declarations, similar to other declarative UI systems like HTML. + +This means that child elements are declared _inside_ their parent elements. The common way to do this with clay element macros is to pass a block: `{}` as the `children` argument, and define child components inside the braces. +```C +// Parent element +CLAY_CONTAINER(id, layout, { + // Child element 1 + CLAY_TEXT(id, text, config); + // Child element 2 + CLAY_RECTANGLE(id, layout, config, { + // etc + }); +}); +``` + +However, unlike HTML and other declarative DSLs, these macros are 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) { + CLAY_RECTANGLE(id, layout, config, { + CLAY_TEXT(id, buttonText, config); + }); +} + +// Parent element +CLAY_CONTAINER(id, layout, { + // Render a bunch of text elements + for (int i = 0; i < textArray.length; i++) { + CLAY_TEXT(id, textArray.elements[i], config); + } + // Only render this element if we're on a mobile screen + if (isMobileScreen) { + CLAY_CONTAINER(id, layout, { + // etc + }); + } + // Re-usable components + ButtonComponent(CLAY_STRING("Click me!")); + ButtonComponent(CLAY_STRING("No, click me!")); +}); +``` + +### Configuring Layout and Styling UI Elements +Many of the element macros in Clay take a `Clay_LayoutConfig` as the second argument. Clay provides a convenience macro, [CLAY_LAYOUT()](#clay_layout) for easy construction of element styles. +```C +CLAY_CONTAINER(id, CLAY_LAYOUT(.padding = {.x = 8, .y = 8}, .backgroundColor = {120, 120, 120, 255}), { + // ... +}); +``` +This macro isn't magic - all it's doing is wrapping the standard designated initializer syntax and adding the result to an internal array. e.g. `(Clay_LayoutConfig) { .padding = { .x = 8, .y = 8 } ...`. + +See the [Clay_LayoutConfig](#clay_layout) API for the full list of options. + +A `Clay_LayoutConfig` struct can be defined in file scope or elsewhere, as long as the lifetime ends after `EndLayout` is called. +```C +// Define a style in the global / file scope +Clay_LayoutConfig reusableStyle = (Clay_LayoutConfig) {.backgroundColor = {120, 120, 120, 255}}; + +CLAY_CONTAINER(id, &reusableStyle, { + // ... +}); +``` + +Some of the other element macros, such as [CLAY_TEXT()](#clay_text) and [CLAY_RECTANGLE()](#clay_rectangle) take an element-specific config object as their 3rd argument. These config objects also have convenience macros for constructing them, generally of the form [CLAY_TEXT_CONFIG()](#clay_text_config) or [CLAY_RECTANGLE_CONFIG()](#clay_rectangle_config): + +```C +CLAY_TEXT(id, CLAY_STRING("button text"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_LATO, .textColor = {255, 255, 255, 255})); +``` + +See the [Full API](#api) for details on the specific config macros. + +### Element IDs + +All element macros take a `uint32_t` ID as their first argument. Clay provides the [CLAY_ID()](#clay_id) macro to generate these IDs as string hashes: +```C +// Will always produce the same ID from the same input string +CLAY_CONTAINER(CLAY_ID("OuterContainer"), style, {}); +``` + +To avoid having to construct dynamic strings at runtime to differentiate ids, 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_CONTAINER(CLAY_IDI("Item", index), style, {}); +} +``` + +**_Generally, you should try to keep IDs unique if possible._** + +This ID is used for querying mouse / pointer events, and 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 a very simple unified API for handling mouse and pointer interactions, with specific handling left to user code. + +All pointer interactions depend on the function `void Clay_SetPointerPosition(Clay_Vector2 position)` being called after each mouse position update and before any other clay functions. + +The function `bool Clay_PointerOver(uint32_t id)` takes an element id that was used during layout creation and returns a bool representing whether the current pointer position is within its bounding box. +```C +// Reminder: Clay_SetPointerPosition must be called before functions that rely on pointer position otherwise it will have no effect +Clay_Vector2 mousePosition = { x, y }; +Clay_SetPointerPosition(mousePosition); +// ... +// If profile picture was clicked +if (mouseButtonDown(0) && Clay_PointerOver(CLAY_ID("ProfilePicture"))) { + // Handle profile picture clicked +} +``` + +Querying `Clay_PointerOver` also works _during_ layout construction, and can be used as a convenient way for applying "hover" states, for example: + +```C +// Reminder: Clay_SetPointerPosition must be called before functions that rely on pointer position otherwise it will have no effect +Clay_Vector2 mousePosition = { x, y }; +Clay_SetPointerPosition(mousePosition); +// ... +uint32_t buttonId = CLAY_ID("HeaderButton"); +// An orange button that turns blue when hovered +CLAY_CONTAINER(buttonId, CLAY_LAYOUT(.backgroundColor = Clay_PointerOver(buttonId) ? COLOR_BLUE : COLOR_ORANGE), { + CLAY_TEXT(CLAY_IDI("Button", index), text, &headerTextConfig); +}); +``` +Note that the bounding box queried by `Clay_PointerOver` is from the last frame. This 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 Containers + +Scrolling containers are defined with the `CLAY_SCROLL_CONTAINER` element macro and function just like normal containers, however to make scroll containers respond to mouse wheel and scroll events, two functions need to be called: +```C +// Reminder: Clay_SetPointerPosition must be called before Clay_UpdateScrollContainers otherwise it will have no effect +Clay_Vector2 mousePosition = { x, y }; +Clay_SetPointerPosition(mousePosition); +// Clay_UpdateScrollContainers needs to be called before Clay_BeginLayout for the position to avoid a 1 frame delay +Clay_UpdateScrollContainers( + true, // Did the mouse click or touch occur this frame? + 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 +); +// ... +``` + +More specific details can be found in the full [Scroll Container API](#clay_scroll_container). + +### Floating Containers ("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"** elements are defined with the `CLAY_FLOATING_CONTAINER` element macro and 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. +Aside from positioning, `CLAY_FLOATING_CONTAINER` elements function like standard `CLAY_CONTAINER` 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_CONTAINER(CLAY_ID("Outer"), CLAY_LAYOUT(.layoutDirection = TOP_TO_BOTTOM), { + CLAY_TEXT(CLAY_ID("Button"), text, &headerTextConfig); + CLAY_FLOATING_TEXT(CLAY_ID("Tooltip"), &CLAY_LAYOUT_DEFAULT, CLAY_FLOATING_CONFIG); + CLAY_TEXT(CLAY_ID("Button"), text, &headerTextConfig); +}); +``` + +More specific details can be found in the full [Floating Container API](#clay_floating_container). + +### 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 simple, singular API for layout out custom elements: +```C +// Extend CLAY_CUSTOM_ELEMENT_CONFIG with your custom data +#define CLAY_EXTEND_CONFIG_CUSTOM struct t_CustomElementData customData; +// Extensions need to happen _before_ the clay include +#include "clay.h" + +// A rough example of how you could handle laying out 3d models in your UI +typedef struct t_CustomElementData { + CustomElementType type; + union { + Model model; + Video video; + // ... + }; +} CustomElementData; + +Model myModel = Load3DModel(filePath); +CustomElement modelElement = (CustomElement) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel } +// ... +CLAY_CONTAINER(id, style, { + // This config is type safe and contains the CustomElementData struct + CLAY_CUSTOM_ELEMENT(id, style, CLAY_CUSTOM_ELEMENT_CONFIG(.customData = { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel })) +}); + +// Later during your rendering +switch (renderCommand->commandType) { + // ... + case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { + // Your extended struct is passed through + CustomElementData *data = renderCommand->elementConfig.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 Container API](#clay_custom_element). + +### 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-canvas/index.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 +- Their rendering color has an alpha value > zero, for example `renderCommand.rectangle.color` or `renderCommand.text.color`. + +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_MAX_ELEMENT_COUNT` - Controls the maximum number of clay elements that memory is pre-allocated for. Defaults to **8192**, which should be more than enough for the majority of use cases. Napkin math is ~450 bytes of memory overhead per element (8192 elements is ~3.5mb of memory) +- `CLAY_DISABLE_CULLING` - Disables [Visibility Culling](#visibility-culling) of render commands. +- `CLAY_WASM` - Required when targeting Web Assembly. +- `CLAY_OVERFLOW_TRAP` - By default, clay will continue to allow function calls without crashing even when it exhausts all its available pre-allocated memory. This can produce erroneous layout results that are difficult to interpret. If `CLAY_OVERFLOW_TRAP` is defined, clay will raise a `SIGTRAP` signal that will be caught by your debugger. Relies on `signal.h` being available in your environment. +- `CLAY_DEBUG` - Used for debugging clay's internal implementation. Useful if you want to modify or debug clay, or learn how things work. It enables a number of debug features such as preserving source strings for has IDs to make debugging easier. +- `CLAY_EXTEND_CONFIG_RECTANGLE` - Provide additional struct members to `CLAY_RECTANGLE_CONFIG` that will be passed through with output render commands. +- `CLAY_EXTEND_CONFIG_TEXT` - Provide additional struct members to `CLAY_TEXT_CONFIG` that will be passed through with output render commands. +- `CLAY_EXTEND_CONFIG_IMAGE` - Provide additional struct members to `CLAY_IMAGE_CONFIG` that will be passed through with output render commands. +- `CLAY_EXTEND_CONFIG_CUSTOM` - Provide additional struct members to `CLAY_IMAGE_CONFIG` that will be passed through with output render commands. + +# 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_SetMeasureTextFunction` -> `Clay_Initialize` + +**Each Frame** +`Clay_SetPointerPosition` -> `Clay_UpdateScrollContainers` -> `Clay_BeginLayout` -> `CLAY_CONTAINER() etc...` -> `Clay_EndLayout` + +### Clay_MinMemorySize + +`uint32_t Clay_MinMemorySize()` + +Returns the minimum amount of memory **in bytes** that clay needs to accomodate 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_String *text, Clay_TextElementConfig *config))` + +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: 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_Initialize + +`void Clay_Initialize(Clay_Arena arena)` + +Takes a `Clay_Arena` and initializes the internal memory mapping. + +### Clay_SetPointerPosition + +`void Clay_SetPointerPosition(Clay_Vector2 position)` + +Sets the internal pointer position (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) + +### Clay_UpdateScrollContainers + +`void Clay_UpdateScrollContainers(bool isPointerActive, 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. + +Touch scrolling only occurs if the `isPointerActive` parameter is `true`, **and** [Clay_SetPointerPosition](#clay_setpointerposition) 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_BeginLayout + +`void Clay_BeginLayout(int screenWidth, int screenHeight)` + +Prepares clay to calculate a new layout. Called each frame / layout **before** any of the [Element Macros](#element-macros). `screenWidth` and `screenHeight` don't neccessarily have to be the screen or window height - you can use clay to lay out an arbitrary sub-section of a window. + +### Clay_EndLayout + +`Clay_RenderCommandArray Clay_EndLayout(int screenWidth, int screenHeight)` + +Ends declaration of element macros and calculates the results of the currrent layout. Renders a [Clay_RenderCommandArray](#clay_rendercommandarrray) containing the results of the layout calculation. + +### Clay_PointerOver + +`bool Clay_PointerOver(uint32_t id)` + +Returns `true` if the pointer position previously set with `Clay_SetPointerPosition` 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. + +## Element Macros + +### CLAY_CONTAINER +**Usage** + +`CLAY_CONTAINER(uint32_t id, Clay_LayoutConfig *layoutConfig, children);` + +**Lifecycle** + +`Clay_BeginLayout()` -> `CLAY_CONTAINER()` -> `Clay_EndLayout()` + +**Notes** + +**CONTAINER** is a generic rectangular container that supports child elements. It uses a [Clay_LayoutConfig](#clay_layout) for styling and layout. + +**Examples** +```C +// Define a container with 16px of x and y padding +CLAY_CONTAINER(CLAY_ID("SideBar"), CLAY_LAYOUT(.padding = {16, 16}), { + // A nested child container + CLAY_CONTAINER(CLAY_ID("SideBar"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16), { + // Children laid out top to bottom + }); +}); +``` + +**Rendering** + +`CLAY_CONTAINER` elements will not generate any render commands. + +### CLAY_TEXT +**Usage** + +`CLAY_TEXT(uint32_t id, 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. + +**Examples** + +```C +// Define a font somewhere in your code +const uint32_t FONT_ID_LATO = 3; +// .. +CLAY_TEXT(CLAY_ID("Username"), CLAY_STRING("John Smith"), CLAY_TEXT_CONFIG(.fontId = FONT_ID_LATO, .fontSize = 24, .textColor = {255, 0, 0, 255})); +// Rendering example +Font fontToUse = LoadedFonts[renderCommand->elementConfig.textElementConfig->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_IMAGE +**Usage** + +`CLAY_IMAGE(id, layoutConfig, imageConfig, children);` + +**Lifecycle** + +`Clay_BeginLayout()` -> `CLAY_IMAGE()` -> `Clay_EndLayout()` + +**Notes** + +**IMAGE_CONTAINER** is a used to layout images, and can optionally have children.It uses [Clay_LayoutConfig](#clay_layout) for styling and layout, and [Clay_ImageElementConfig]() to configure image specific options. + +**Examples** + +```C +// Load an image somewhere in your code +Image profilePicture = LoadImage("profilePicture.png"); +// .. +CLAY_IMAGE(CLAY_ID("ProfilePicture"), &CLAY_LAYOUT_DEFAULT, CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .height = 60, .width = 60), {}); +// Rendering example +Image *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->elementConfig.imageElementConfig->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->color` with the image. + +### CLAY_SCROLL_CONTAINER +**Usage** + +`CLAY_SCROLL_CONTAINER(id, layoutConfig, scrollConfig, children);` + +**Lifecycle** + +`Clay_SetPointerPosition()` -> `Clay_UpdateScrollContainers()` -> `Clay_BeginLayout()` -> `CLAY_SCROLL_CONTAINER()` -> `Clay_EndLayout()` + +**Notes** + +**SCROLL_CONTAINER** creates a masked container that allows layout of children to extend beyond its boundaries.It uses [Clay_LayoutConfig](#clay_layout) for styling and layout, and [Clay_ScrollElementConfig]() to configure scroll specific options. + +Note: In order to process scrolling based on pointer position and mouse wheel or touch interactions, you must call `Clay_SetPointerPosition()` and `Clay_UpdateScrollContainers()` _before_ calling `BeginLayout`. + +**Examples** + +```C +CLAY_SCROLL_CONTAINER(CLAY_ID("MainContent"), &CLAY_LAYOUT_DEFAULT, CLAY_SCROLL_CONFIG(.vertical = true), { + // Create child content with a fixed height of 5000 + CLAY_CONTAINER(CLAY_ID("ScrollInner"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(5000) }), {}); +}); +``` + +**Rendering** + +Scroll containers will result in two 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) + +### CLAY_BORDER_CONTAINER +**Usage** + +`CLAY_BORDER_CONTAINER(id, layoutConfig, borderConfig, children);` + +**Lifecycle** + +`Clay_BeginLayout()` -> `CLAY_BORDER_CONTAINER()` -> `Clay_EndLayout()` + +**Notes** + +**BORDER_CONTAINER** is functionally identical to **CONTAINER** but also allows configuration of a border around the element. It uses [Clay_LayoutConfig](#clay_style) for styling and layout, and [Clay_BorderElementConfig]() to configure border specific options. + +**Examples** + +```C +// 300x300 container with a 1px red border around all the edges +CLAY_BORDER_CONTAINER(CLAY_ID("OuterBorder"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIXED(300)}), CLAY_BORDER_CONFIG_OUTSIDE(.color = COLOR_RED, .width = 1), { + // ... +}); + +// Container with a 3px yellow bottom border +CLAY_BORDER_CONTAINER(CLAY_ID("OuterBorder"), &CLAY_LAYOUT_DEFAULT, CLAY_BORDER_CONFIG(.bottom = { .color = COLOR_YELLOW, .width = 3 }), { + // ... +}); + +// Container with a 5px curved border around the edges, and a 5px blue border between all children laid out top to bottom +CLAY_BORDER_CONTAINER(CLAY_ID("OuterBorder"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM), CLAY_BORDER_CONFIG_ALL_RADIUS({ .color = COLOR_BLUE, .width = 5 }), { + // Child + // -- 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 [renderers]() for examples of how to draw borders using line and curve primitives. + +### CLAY_FLOATING_CONTAINER +**Usage** + +`CLAY_FLOATING_CONTAINER(id, layoutConfig, floatingConfig, children)` + +**Lifecycle** + +`Clay_BeginLayout()` -> `CLAY_FLOATING_CONTAINER()` -> `Clay_EndLayout()` + +**Notes** + +**FLOATING_CONTAINER** 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 `CLAY_CONTAINER` 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 use [Clay_LayoutConfig](#clay_style) for styling and layout, and [Clay_FloatingElementConfig]() to configure specific options. + +**Examples** + +```C +// Horizontal container with three option buttons +CLAY_CONTAINER(CLAY_ID("OptionsList"), CLAY_LAYOUT(.childGap = 16), { + CLAY_RECTANGLE(CLAY_IDI("Option", 1), CLAY_LAYOUT(.padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = COLOR_BLUE), { + CLAY_TEXT(CLAY_IDI("OptionText", 1), CLAY_STRING("Option 1"), CLAY_TEXT_CONFIG()); + }); + CLAY_RECTANGLE(CLAY_IDI("Option", 2), CLAY_LAYOUT(.padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = COLOR_BLUE), { + CLAY_TEXT(CLAY_IDI("OptionText", 2), 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_FLOATING_CONTAINER(CLAY_ID("OptionTooltip"), &CLAY_LAYOUT_DEFAULT, CLAY_FLOATING_CONFIG(.zIndex = 1, .offset = {.x = 0, .y = -80}), { + CLAY_TEXT(CLAY_IDI("OptionTooltipText", 1), CLAY_STRING("Most popular!"), CLAY_TEXT_CONFIG()); + }); + }); + CLAY_RECTANGLE(CLAY_IDI("Option", 3), CLAY_LAYOUT(.padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = COLOR_BLUE), { + CLAY_TEXT(CLAY_IDI("OptionText", 3), 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_CONTAINER(CLAY_IDI("Option", i + 1), &CLAY_LAYOUT_DEFAULT, {}) { + // ... + } +} +// Note the use of "parentId". +// Floating tooltip will attach above the "Option 2" container and not affect widths or positions of other elements +CLAY_FLOATING_CONTAINER(CLAY_ID("OptionTooltip"), &CLAY_LAYOUT_DEFAULT, CLAY_FLOATING_CONFIG(.parentId = CLAY_IDI("Option", 2) .zIndex = 1, .offset = {.x = 0, .y = -80}), { + CLAY_TEXT(CLAY_IDI("OptionTooltipText", 1), 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_FLOATING_CONTAINER` is called. + +**Rendering** + +`CLAY_FLOATING_CONTAINER` elements will not generate any render commands. + + +### CLAY_CUSTOM_ELEMENT +**Usage** + +`CLAY_CUSTOM_ELEMENT(uint32_t id, Clay_LayoutConfig *layoutConfig, Clay_CustomElementConfig *customElementConfig);` + +**Lifecycle** + +`Clay_BeginLayout()` -> `CLAY_CUSTOM_ELEMENT()` -> `Clay_EndLayout()` + +**Notes** + +**CUSTOM_ELEMENT** uses [Clay_LayoutConfig](#clay_style) for styling and layout, but has no children and allows the user to pass custom data to the renderer. + +**Examples** +```C +// Extend CLAY_CUSTOM_ELEMENT_CONFIG with your custom data +#define CLAY_EXTEND_CONFIG_CUSTOM struct t_CustomElementData customData; +// Extensions need to happen _before_ the clay include +#include "clay.h" + +// A rough example of how you could handle laying out 3d models in your UI +typedef struct t_CustomElementData { + CustomElementType type; + union { + Model model; + Video video; + // ... + }; +} CustomElementData; + +Model myModel = Load3DModel(filePath); +CustomElement modelElement = (CustomElement) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel } +// ... +CLAY_CONTAINER(id, style, { + // This config is type safe and contains the CustomElementData struct + CLAY_CUSTOM_ELEMENT(id, style, CLAY_CUSTOM_ELEMENT_CONFIG(.customData = { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel })) +}); + +// Later during your rendering +switch (renderCommand->commandType) { + // ... + case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { + // Your extended struct is passed through + CustomElementData *data = renderCommand->elementConfig.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. +The user will need to access `renderCommand.data.custom.customData` to retrieve custom data referenced during layout creation. + +## Config Macros + +A number of clay element macros take element-specific **config** structs. + +### CLAY_LAYOUT + +**CLAY_LAYOUT()** is used for configuring layout for most clay elements. + +**Struct API (Pseudocode)** + +```C +// CLAY_LAYOUT(.member = value) supports these options +Clay_LayoutConfig { + Clay_LayoutDirection layoutDirection = CLAY_LEFT_TO_RIGHT (default) | CLAY_TOP_TO_BOTTOM; + Clay_Padding padding { + float x; float y; + }; + 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(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(height) | CLAY_SIZING_PERCENT(float percent) + }; // See CLAY_SIZING_GROW() etc for more details +}; +``` +As with all config macros, `CLAY_LAYOUT()` accepts designated initializer syntax and provides default values for any unspecified struct members. + +**Fields** + +**`.layoutDirection`** - `Clay_LayoutDirection` + +`CLAY_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_LAYOUT(.padding = { .x = 16, .y = 16 })` + +Controls horizontal and vertical white-space "padding" around the **outside** of child elements. + +Screenshot 2024-08-22 at 10 50 49 AM + +--- + +**`.childGap`** - `uint16_t` + +`CLAY_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_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_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_CONTAINER(CLAY_ID("Button"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW() }, .padding = {16, 16}, .childGap = 16), { + // Children will be laid out vertically with 16px of padding around and between +}); +``` + +### CLAY_RECTANGLE_CONFIG + +**CLAY_RECTANGLE_CONFIG()** is used for configuring rendering for [CLAY_RECTANGLE()]() elements. The config will be passed through to render commands as `Clay_RenderCommand.config.rectangleElementConfig` + +**Struct API (Pseudocode)** + +```C +// CLAY_RECTANGLE_CONFIG(.member = value) supports these options +Clay_RectangleConfig { + Clay_Color color { + float r; float g; float b; float a; + }; + float cornerRadius; + + #ifdef CLAY_EXTEND_CONFIG_RECTANGLE + // Contents of CLAY_EXTEND_CONFIG_RECTANGLE will be pasted here + #endif +} +``` + +As with all config macros, `CLAY_RECTANGLE_CONFIG()` accepts designated initializer syntax and provides default values for any unspecified struct members. + +**Extension** + +The underlying `Clay_RectangleElementConfig` can be extended with new members by using: +```C +#define CLAY_EXTEND_CONFIG_RECTANGLE float newField; +#include "clay.h" // Define your extension before including clay.h +``` + +**Fields** + +**`.color`** - `Clay_Color` + +`CLAY_RECTANGLE_CONFIG(.color = {120, 120, 120, 255})` + +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_RECTANGLE_CONFIG(.cornerRadius = 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_CONFIG(.cornerRadius = CLAY_CORNER_RADIUS(10))` + +### CLAY_TEXT_CONFIG + +**CLAY_TEXT_CONFIG()** is a macro used to create and store `Clay_TextElementConfig` structs, which are for configuring [CLAY_TEXT]() elements. The config used in declaration will be passed both as an argument to the user-provided `Clay_MeasureText(Clay_String *text, Clay_TextElementConfig *config)` function as well as the in the final output as `Clay_RenderCommand.config.textElementConfig`. + +**Struct API (Pseudocode)** + +```C +// 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 lineSpacing; + + #ifdef CLAY_EXTEND_CONFIG_TEXT + // Contents of CLAY_EXTEND_CONFIG_TEXT will be pasted here + #endif +}; +``` +As with all config macros, `CLAY_TEXT_CONFIG()` accepts designated initializer syntax and provides default values for any unspecified struct members. + +**Extension** + +The underlying `Clay_TextElementConfig` can be extended with new members by using: +```C +#define CLAY_EXTEND_CONFIG_TEXT float newField; +#include "clay.h" // Define your extension before including clay.h +``` + +**Fields** + +**`.textColor`** + +`CLAY_TEXT_CONFIG(.textColor = {120, 120, 120, 255})` + +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. + +--- + +**`.lineSpacing`** + +`CLAY_TEXT_CONFIG(.lineSpacing = 1)` + +`.lineSpacing` results in **vertical** white space between lines of text (from both `\n` characters and text wrapping) and will affect layout of parents and siblings. + +**Example Usage** +```C +// A 24px, red text element that says "John Smith" +CLAY_TEXT(CLAY_ID("Username"), CLAY_STRING("John Smith"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = 2, .textColor = {255, 0, 0, 255})); +``` + +### CLAY_IMAGE_CONFIG + +**CLAY_IMAGE_CONFIG()** is a macro used to create and store `Clay_ImageElementConfig` structs, which are for configuring [CLAY_IMAGE]() elements. The config will be passed through to render commands as `Clay_RenderCommand.config.imageElementConfig` + +**Struct API (Pseudocode)** + +```C +Clay_ImageElementConfig { + Clay_Dimensions sourceDimensions { + float width; float height; + }; + // -- + #ifndef CLAY_EXTEND_CONFIG_IMAGE + void * imageData; // Note: This field will be replaced if #define CLAY_EXTEND_CONFIG_IMAGE is specified + #else CLAY_EXTEND_CONFIG_IMAGE + // Contents of CLAY_EXTEND_CONFIG_IMAGE will be pasted here + #endif +}; +``` + +As with all config macros, `CLAY_IMAGE_CONFIG()` accepts designated initializer syntax and provides default values for any unspecified struct members. + +**Extension** + +The underlying `Clay_ImageElementConfig` can be extended with new members by using: +```C +#define CLAY_EXTEND_CONFIG_IMAGE float newField; +#include "clay.h" // Define your extension before including clay.h +``` + +**Fields** + +**`.sourceDimensions`** - `Clay_Dimensions` + +`CLAY_IMAGE_CONFIG(.sourceDimensions = { 1024, 768 })` + +Used to perform **aspect ratio scaling** on the image element. As of this version of clay, aspect ratio scaling only applies to the `height` of an image (i.e. image height will scale with width growth and limitations, but width will not scale with height growth and limitations) + +--- + +**`.imageData`** - `void *` + +`CLAY_IMAGE_CONFIG(.imageData = &myImage)` + +`.imageData` is a generic void pointer that can be used to pass through image data to the renderer. **Note:** this field is generally not recommended for usage due to the lack of type safety, see `#define CLAY_EXTEND_CONFIG_IMAGE` in [Preprocessor Directives]() for an alternative. + +```C +// Load an image somewhere in your code +Image profilePicture = LoadImage("profilePicture.png"); +// Note that when rendering, .imageData will be void* type. +CLAY_IMAGE(CLAY_ID("ProfilePicture"), &CLAY_LAYOUT_DEFAULT, CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .sourceDimensions = { 60, 60 }), {}); + +// OR ---------------- + +// Extend CLAY_CUSTOM_IMAGE_CONFIG with your custom image format +#define CLAY_EXTEND_CONFIG_IMAGE struct t_Image image; +// Extensions need to happen _before_ the clay include +#include "clay.h" + +typedef struct t_Image { + ImageFormat format; + u8int_t *internalData; + // ... etc +} Image; + +// You can now use CLAY_IMAGE_CONFIG with your custom type and still have type safety & code completion +CLAY_IMAGE(CLAY_ID("ProfilePicture"), &CLAY_LAYOUT_DEFAULT, CLAY_IMAGE_CONFIG(.image = { .format = IMAGE_FORMAT_RGBA, .internalData = &imageData }, .sourceDimensions = { 60, 60 }), {}); +``` + +### CLAY_FLOATING_CONFIG + +**CLAY_FLOATING_CONFIG()** is a macro used to create and store `Clay_FloatingElementConfig` structs, which are for configuring [CLAY_FLOATING_CONTAINER]() elements. + +**Struct Definition (Pseudocode)** + +```C +typedef struct +{ + Clay_Vector2 offset { + float x, float y + }; + Clay_Dimensions expand { + float width, float height + }; + uint16_t zIndex; + uint32_t parentId; + Clay_FloatingAttachPoints attachment { + .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_FloatingElementConfig; +``` + +As with all config macros, `CLAY_FLOATING_CONFIG()` accepts designated initializer syntax and provides default values for any unspecified struct members. + +**Fields** + +**`.offset`** - `Clay_Vector2` + +`CLAY_FLOATING_CONFIG(.offset = { -24, -24 })` + +Used to apply a position offset to the floating container _after_ all other layout has been calculated. + +--- + +**`.expand`** - `Clay_Dimensions` + +`CLAY_FLOATING_CONFIG(.expand = { 16, 16 })` + +Used to expand the width and height of the floating container _before_ laying out child elements. + +--- + +**`.zIndex`** - `float` + +`CLAY_FLOATING_CONFIG(.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_FLOATING_CONFIG(.parentId = CLAY_ID("HeaderButton"))` + +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()` 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_CONTAINER(CLAY_IDI("SidebarButton", 1), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents + if (tooltip.attachedButtonIndex == 1) { + CLAY_FLOATING_CONTAINER(/* floating config... */); + } +}); +CLAY_CONTAINER(CLAY_IDI("SidebarButton", 2), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents + if (tooltip.attachedButtonIndex == 2) { + CLAY_FLOATING_CONTAINER(/* floating config... */); + } +}); +CLAY_CONTAINER(CLAY_IDI("SidebarButton", 3), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents + if (tooltip.attachedButtonIndex == 3) { + CLAY_FLOATING_CONTAINER(/* floating config... */); + } +}); +CLAY_CONTAINER(CLAY_IDI("SidebarButton", 4), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents + if (tooltip.attachedButtonIndex == 4) { + CLAY_FLOATING_CONTAINER(/* floating config... */); + } +}); +CLAY_CONTAINER(CLAY_IDI("SidebarButton", 5), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents + if (tooltip.attachedButtonIndex == 5) { + CLAY_FLOATING_CONTAINER(/* 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_CONTAINER(CLAY_IDI("SidebarButton", 1), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents +}); +CLAY_CONTAINER(CLAY_IDI("SidebarButton", 2), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents +}); +CLAY_CONTAINER(CLAY_IDI("SidebarButton", 3), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents +}); +CLAY_CONTAINER(CLAY_IDI("SidebarButton", 4), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents +}); +CLAY_CONTAINER(CLAY_IDI("SidebarButton", 5), &CLAY_LAYOUT_DEFAULT, { + // .. some button contents +}); + +// Any other point in the hierarchy +CLAY_FLOATING_CONTAINER(CLAY_ID("OptionTooltip"), &CLAY_LAYOUT_DEFAULT, CLAY_FLOATING_CONFIG(.parentId = CLAY_IDI("SidebarButton", tooltip.attachedButtonIndex)), { + // Tooltip contents... +}); +``` + +--- + +**`.attachment`** - `Clay_FloatingAttachPoints` + +`CLAY_FLOATING_CONFIG(.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_FLOATING_CONFIG(.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) + + +### CLAY_SCROLL_CONFIG + +**CLAY_SCROLL_CONFIG()** is a macro used to create and store `Clay_ScrollContainerElementConfig` structs, which are for configuring [CLAY_SCROLL_CONTAINER]() elements. + +**Struct Definition (Pseudocode)** + +```C +typedef struct +{ + bool horizontal; + bool vertical; +} Clay_ScrollContainerElementConfig; +``` + +As with all config macros, `CLAY_SCROLL_CONFIG()` accepts designated initializer syntax and provides default values for any unspecified struct members. + +**Fields** + +**`.horizontal`** - `bool` + +`CLAY_SCROLL_CONFIG(.horizontal = true)` + +Enables or disables horizontal scrolling for this container element. + +--- + +**`.vertical`** - `bool` + +`CLAY_SCROLL_CONFIG(.vertical = true)` + +Enables or disables vertical scrolling for this container element. + +**Examples** + +```C +CLAY_SCROLL_CONTAINER(CLAY_ID("MainContent"), &CLAY_LAYOUT_DEFAULT, CLAY_SCROLL_CONFIG(.vertical = true), { + // Create child content with a fixed height of 5000 + CLAY_CONTAINER(CLAY_ID("ScrollInner"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(5000) }), {}); +}); +``` + +### CLAY_BORDER_CONFIG + +**CLAY_BORDER_CONFIG()** is a macro used to create and store `Clay_BorderContainerElementConfig` structs, which are for configuring [CLAY_BORDER_CONTAINER]() elements. + +**Struct Definition (Pseudocode)** + +```C +typedef struct +{ + Clay_Border left { + float width; + Clay_Color color { + float r; float g; float b; float a; + }; + }; + Clay_Border right { + float width; + Clay_Color color { + float r; float g; float b; float a; + }; + } + Clay_Border top { + float width; + Clay_Color color { + float r; float g; float b; float a; + }; + }; + Clay_Border bottom { + float width; + Clay_Color color { + float r; float g; float b; float a; + }; + }; + Clay_Border betweenChildren { + float width; + Clay_Color color { + float r; float g; float b; float a; + }; + }; + Clay_CornerRadius cornerRadius { + float topLeft; + float topRight; + float bottomLeft; + float bottomRight; + }; +} Clay_BorderContainerElementConfig; +``` + +**Usage** + +As with all config macros, `CLAY_BORDER_CONFIG()` accepts designated initializer syntax and provides default values for any unspecified struct members. + + +**Fields** + +**`.left, .right, .top, .bottom`** - `Clay_Border` + +`CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 4, COLOR_YELLOW } /* etc */)` + +Indicates to the renderer that a border of `.color` should be draw at the specified edges of the bounding box, **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. + +--- + +**`.betweenChildren`** - `Clay_Border` + +`CLAY_BORDER_CONFIG(.betweenChildren = { 2, 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. + +--- + +**`.cornerRadius`** - `float` + +`CLAY_BORDER_CONFIG(.cornerRadius = 16)` + +Defines the radius in pixels for the arc of border corners (`0` is square, `rectangle.width / 2` is circular). It is up to the renderer to decide how to interpolate between differing border widths and colors across shared corners. + +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_CONFIG(.cornerRadius = CLAY_CORNER_RADIUS(10))` + +**Convenience Macros** + +There are some common cases for border configuration that are repetitive, i.e. specifying the same border around all four edges. Some convenience macros are provided for these cases: + +- `CLAY_BORDER_CONFIG_OUTSIDE(.width = 2, .color = COLOR_RED)` - Shorthand for configuring all 4 outside borders at once.` +- `CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(width, color, radius)` - Shorthand for configuring all 4 outside borders at once, with the provided `.cornerRadius`. Note this is a function-like macro and does not take `.member = value` syntax. +- `CLAY_BORDER_CONFIG_ALL(.width = 2, .color = COLOR_RED)` - Shorthand for configuring all 4 outside borders and `.betweenChildren` at once. +- `CLAY_BORDER_CONFIG_ALL_RADIUS(width, color, radius)` - Shorthand for configuring all 4 outside borders and `.betweenChildren` at once, with the provided `cornerRadius`. Note this is a function-like macro and does not take `.member = value` syntax. + +### CLAY_CUSTOM_ELEMENT_CONFIG + +**CLAY_CUSTOM_ELEMENT_CONFIG()** is a macro used to create and store `Clay_CustomElementConfig` structs, which are for configuring [CLAY_CUSTOM_ELEMENT]() elements. + +**Struct Definition (Pseudocode)** + +```C +typedef struct +{ + #ifndef CLAY_EXTEND_CONFIG_CUSTOM + void * customData; // Note: This field will be replaced if #define CLAY_EXTEND_CONFIG_CUSTOM is specified + #else CLAY_EXTEND_CONFIG_CUSTOM + // Contents of CLAY_EXTEND_CONFIG_CUSTOM will be pasted here + #endif +} Clay_CustomElementConfig; +``` + +As with all config macros, `CLAY_CUSTOM_ELEMENT_CONFIG()` accepts designated initializer syntax and provides default values for any unspecified struct members. + +**Extension** + +The underlying `Clay_ImageCustomConfig` can be extended with new members by using: +```C +#define CLAY_EXTEND_CONFIG_CUSTOM float newField; +#include "clay.h" // Define your extension before including clay.h +``` + +**Fields** + +`.customData` - `void *` + +`CLAY_CUSTOM_CONFIG(.customData = &myCustomData)` + +`.customData` is a generic void pointer that can be used to pass through custom data to the renderer. **Note:** this field is generally not recommended for usage due to the lack of type safety, see `#define CLAY_EXTEND_CONFIG_CUSTOM` in [Preprocessor Directives]() for an alternative. + +```C +// Extend CLAY_CUSTOM_ELEMENT_CONFIG with your custom data +#define CLAY_EXTEND_CONFIG_CUSTOM struct t_CustomElementData customData; +// Extensions need to happen _before_ the clay include +#include "clay.h" + +// A rough example of how you could handle laying out 3d models in your UI +typedef struct t_CustomElementData { + CustomElementType type; + union { + Model model; + Video video; + // ... + }; +} CustomElementData; + +Model myModel = Load3DModel(filePath); +CustomElement modelElement = (CustomElement) { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel } +// ... +CLAY_CONTAINER(id, style, { + // This config is type safe and contains the CustomElementData struct + CLAY_CUSTOM_ELEMENT(id, style, CLAY_CUSTOM_ELEMENT_CONFIG(.customData = { .type = CUSTOM_ELEMENT_TYPE_MODEL, .model = myModel })) +}); + +// Later during your rendering +switch (renderCommand->commandType) { + // ... + case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { + // Your extended struct is passed through + CustomElementData *data = renderCommand->elementConfig.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; + } +} +``` + +## Misc Macros + +### CLAY_ID + +`uint32_t CLAY_ID(char *label)` + +Generates a `uint32_t` string hash from the provided `char *label`. Used both to generate ids when defining element macros, as well as for referencing ids later when using utility functions such as [Clay_PointerOver](#clay-pointerover) + +### CLAY_IDI() + +`uint32_t CLAY_IDI(char *label, int index)` + +Generates a `uint32_t` string hash 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. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..688abaa --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.10 \ No newline at end of file diff --git a/clay.h b/clay.h new file mode 100644 index 0000000..3596e3d --- /dev/null +++ b/clay.h @@ -0,0 +1,2273 @@ +#pragma once + +#ifndef CLAY_IMPLEMENTATION +#define CLAY_IMPLEMENTATION + +#ifdef CLAY_WASM +#define CLAY_WASM_EXPORT(name) __attribute__((export_name(name))) +#else +#define CLAY_WASM_EXPORT(null) +#endif + +#include "stdint.h" +#include "stdbool.h" +#ifdef CLAY_OVERFLOW_TRAP + #include "signal.h" +#endif + +#ifndef CLAY_MAX_ELEMENT_COUNT +#define CLAY_MAX_ELEMENT_COUNT 8192 +#endif + +#ifndef CLAY__NULL +#define CLAY__NULL 0 +#endif + +#ifndef CLAY__MAXFLOAT +#define CLAY__MAXFLOAT 3.40282346638528859812e+38F +#endif + +#define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define CLAY__MIN(x, y) (((x) < (y)) ? (x) : (y)) + +// Publicly visible config macro ----------------------------------------------------- + +#define CLAY_LAYOUT(...) Clay_LayoutConfigArray_Add(&Clay__layoutConfigs, (Clay_LayoutConfig) {__VA_ARGS__ }) + +#define CLAY_RECTANGLE_CONFIG(...) Clay_RectangleElementConfigArray_Add(&Clay__rectangleElementConfigs, (Clay_RectangleElementConfig) {__VA_ARGS__ }) + +#define CLAY_TEXT_CONFIG(...) Clay_TextElementConfigArray_Add(&Clay__textElementConfigs, (Clay_TextElementConfig) {__VA_ARGS__ }) + +#define CLAY_IMAGE_CONFIG(...) Clay_ImageElementConfigArray_Add(&Clay__imageElementConfigs, (Clay_ImageElementConfig) {__VA_ARGS__ }) + +#define CLAY_FLOATING_CONFIG(...) Clay_FloatingElementConfigArray_Add(&Clay__floatingElementConfigs, (Clay_FloatingElementConfig) {__VA_ARGS__ }) + +#define CLAY_CUSTOM_ELEMENT_CONFIG(...) Clay_CustomElementConfigArray_Add(&Clay__customElementConfigs, (Clay_CustomElementConfig) {__VA_ARGS__ }) + +#define CLAY_SCROLL_CONFIG(...) Clay_ScrollContainerElementConfigArray_Add(&Clay__scrollElementConfigs, (Clay_ScrollContainerElementConfig) {__VA_ARGS__ }) + +#define CLAY_BORDER_CONFIG(...) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { __VA_ARGS__ }) + +#define CLAY_BORDER_CONFIG_OUTSIDE(...) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { .left = { __VA_ARGS__ }, .right = { __VA_ARGS__ }, .top = { __VA_ARGS__ }, .bottom = { __VA_ARGS__ } }) + +#define CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(width, color, radius) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { .left = { width, color }, .right = { width, color }, .top = { width, color }, .bottom = { width, color }, .cornerRadius = { radius, radius, radius, radius } }) + +#define CLAY_BORDER_CONFIG_ALL(...) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { .left = { __VA_ARGS__ }, .right = { __VA_ARGS__ }, .top = { __VA_ARGS__ }, .bottom = { __VA_ARGS__ }, .betweenChildren = { __VA_ARGS__ } }) + +#define CLAY_BORDER_CONFIG_ALL_RADIUS(width, color, radius) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { .left = { __VA_ARGS__ }, .right = { __VA_ARGS__ }, .top = { __VA_ARGS__ }, .bottom = { __VA_ARGS__ }, .betweenChildren = { __VA_ARGS__ }, .cornerRadius = { radius, radius, radius, radius }}) + +#define CLAY_CORNER_RADIUS(radius) (Clay_CornerRadius) { radius, radius, radius, radius } + +#define CLAY_SIZING_FIT(...) (Clay_SizingAxis) { .type = CLAY__SIZING_TYPE_FIT, .sizeMinMax = (Clay_SizingMinMax) {__VA_ARGS__} } + +#define CLAY_SIZING_GROW(...) (Clay_SizingAxis) { .type = CLAY__SIZING_TYPE_GROW, .sizeMinMax = (Clay_SizingMinMax) {__VA_ARGS__} } + +#define CLAY_SIZING_FIXED(fixedSize) (Clay_SizingAxis) { .type = CLAY__SIZING_TYPE_GROW, .sizeMinMax = { fixedSize, fixedSize } } + +#define CLAY_SIZING_PERCENT(percentOfParent) (Clay_SizingAxis) { .type = CLAY__SIZING_TYPE_PERCENT, .sizePercent = percentOfParent } + +#define CLAY_ID(label) Clay__HashString(CLAY_STRING(label), 0) + +#define CLAY_IDI(label, index) Clay__HashString(CLAY_STRING(label), index) + +#define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof(s[0])) - sizeof(s[0])) + +#define CLAY_STRING(string) (Clay_String) { .length = CLAY__STRING_LENGTH(string), .chars = string } + +// Publicly visible layout element macros ----------------------------------------------------- +#define CLAY_CONTAINER(id, layoutConfig, children) \ + Clay__OpenContainerElement(id, layoutConfig); \ + children \ + Clay__CloseContainerElement() + +#define CLAY_RECTANGLE(id, layoutConfig, rectangleConfig, children) \ + Clay__OpenRectangleElement(id, layoutConfig, rectangleConfig); \ + children \ + Clay__CloseContainerElement() + +#define CLAY_TEXT(id, text, textConfig) Clay__OpenTextElement(id, text, textConfig) + +#define CLAY_IMAGE(id, layoutConfig, imageConfig, children) \ + Clay__OpenImageContainerElement(id, layoutConfig, imageConfig); \ + children \ + Clay__CloseContainerElement() + +#define CLAY_SCROLL_CONTAINER(id, layoutConfig, scrollConfig, children) \ + Clay__OpenScrollElement(id, layoutConfig, scrollConfig); \ + children \ + Clay__CloseScrollContainerElement() + +#define CLAY_FLOATING_CONTAINER(id, layoutConfig, floatingConfig, children) \ + Clay__OpenFloatingContainerElement(id, layoutConfig, floatingConfig); \ + children \ + Clay__CloseFloatingContainer() + +#define CLAY_BORDER_CONTAINER(id, layoutConfig, borderConfig, children) \ + Clay__OpenBorderContainerElement(id, layoutConfig, borderConfig); \ + children \ + Clay__CloseContainerElement() + +#define CLAY_CUSTOM_ELEMENT(id, layoutConfig, customElementConfig, children) \ + Clay__OpenCustomElement(id, layoutConfig, customElementConfig); \ + children \ + Clay__CloseContainerElement() + +// 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 { + int length; + const char *chars; +} Clay_String; + +Clay_String CLAY__SPACECHAR = (Clay_String) { .length = 1, .chars = " " }; +Clay_String CLAY__STRING_DEFAULT = (Clay_String) { .length = 0, .chars = "" }; + +typedef struct { + Clay_String label; + uint64_t nextAllocation; + uint64_t capacity; + char *memory; +} Clay_Arena; + +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_String *internalArray; +} Clay_StringArray; + +Clay_StringArray Clay_warnings = (Clay_StringArray) {}; + +Clay_String *Clay_StringArray_Add(Clay_StringArray *array, Clay_String item) +{ + if (array->length < array->capacity) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + else { + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + } + return &CLAY__STRING_DEFAULT; +} + +Clay_StringArray Clay_StringArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) +{ + uint64_t totalSizeBytes = capacity * sizeof(Clay_String); + Clay_StringArray array = (Clay_StringArray){.capacity = capacity, .length = 0}; + uint64_t arenaOffsetAligned = arena->nextAllocation + (arena->nextAllocation % sizeof(Clay_String)); + if (arenaOffsetAligned + totalSizeBytes <= arena->capacity) { + array.internalArray = (Clay_String*)(arena->memory + arenaOffsetAligned); + arena->nextAllocation = arenaOffsetAligned + totalSizeBytes; + } + else { + Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow.")); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + } + return array; +} + +void* Clay__Array_Allocate_Arena(uint32_t capacity, uint32_t itemSize, Clay_Arena *arena) +{ + uint64_t totalSizeBytes = capacity * itemSize; + uint64_t arenaOffsetAligned = arena->nextAllocation + (arena->nextAllocation % itemSize); + if (arenaOffsetAligned + totalSizeBytes <= arena->capacity) { + arena->nextAllocation = arenaOffsetAligned + totalSizeBytes; + return (void*)(arena->memory + arenaOffsetAligned); + } + else { + Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow.")); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + } + return CLAY__NULL; +} + +bool Clay__Array_RangeCheck(int index, uint32_t length) +{ + if (index < length && index >= 0) { + return true; + } + Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Array access out of bounds.")); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + return false; +} + +bool Clay__Array_IncrementCapacityCheck(uint32_t length, uint32_t capacity) +{ + if (length < capacity) { + return true; + } + Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to add to array that is already at capacity.")); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + return false; +} + +bool CLAY__BOOL_DEFAULT = false; + +// __GENERATED__ template array_define TYPE=bool NAME=Clay__BoolArray +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + bool *internalArray; +} Clay__BoolArray; + +Clay__BoolArray Clay__BoolArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay__BoolArray){.capacity = capacity, .length = 0, .internalArray = (bool *)Clay__Array_Allocate_Arena(capacity, sizeof(bool), arena)}; +} +#pragma endregion +// __GENERATED__ template + +typedef struct { + float r, g, b, a; +} Clay_Color; + +typedef struct { + float x, y, width, height; +} Clay_Rectangle; + +typedef struct { + float width, height; +} Clay_Dimensions; + +typedef struct { + float x, y; +} Clay_Vector2; + +typedef enum __attribute__((__packed__)) { + CLAY_LEFT_TO_RIGHT, + CLAY_TOP_TO_BOTTOM, +} Clay_LayoutDirection; + +typedef enum __attribute__((__packed__)) { + CLAY_ALIGN_X_LEFT, + CLAY_ALIGN_X_RIGHT, + CLAY_ALIGN_X_CENTER, +} Clay_LayoutAlignmentX; + +typedef enum __attribute__((__packed__)) { + CLAY_ALIGN_Y_TOP, + CLAY_ALIGN_Y_BOTTOM, + CLAY_ALIGN_Y_CENTER, +} Clay_LayoutAlignmentY; + +typedef enum __attribute__((__packed__)) { + CLAY__SIZING_TYPE_FIT, + CLAY__SIZING_TYPE_GROW, + CLAY__SIZING_TYPE_PERCENT, +} Clay__SizingType; + +typedef enum { + CLAY_RENDER_COMMAND_TYPE_NONE, + CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + CLAY_RENDER_COMMAND_TYPE_BORDER, + CLAY_RENDER_COMMAND_TYPE_TEXT, + CLAY_RENDER_COMMAND_TYPE_IMAGE, + CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, + CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, + CLAY_RENDER_COMMAND_TYPE_CUSTOM, +} Clay_RenderCommandType; + +typedef enum __attribute__((__packed__)) { + CLAY__LAYOUT_ELEMENT_TYPE_CONTAINER, + CLAY__LAYOUT_ELEMENT_TYPE_RECTANGLE, + CLAY__LAYOUT_ELEMENT_TYPE_BORDER_CONTAINER, + CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER, + CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER, + CLAY__LAYOUT_ELEMENT_TYPE_IMAGE, + CLAY__LAYOUT_ELEMENT_TYPE_TEXT, + CLAY__LAYOUT_ELEMENT_TYPE_CUSTOM, +} Clay__LayoutElementType; + +Clay_RenderCommandType Clay__LayoutElementTypeToRenderCommandType[] = { + [CLAY__LAYOUT_ELEMENT_TYPE_CONTAINER] = CLAY_RENDER_COMMAND_TYPE_NONE, + [CLAY__LAYOUT_ELEMENT_TYPE_RECTANGLE] = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + [CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER] = CLAY_RENDER_COMMAND_TYPE_NONE, + [CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER] = CLAY_RENDER_COMMAND_TYPE_NONE, + [CLAY__LAYOUT_ELEMENT_TYPE_BORDER_CONTAINER] = CLAY_RENDER_COMMAND_TYPE_BORDER, + [CLAY__LAYOUT_ELEMENT_TYPE_IMAGE] = CLAY_RENDER_COMMAND_TYPE_IMAGE, + [CLAY__LAYOUT_ELEMENT_TYPE_TEXT] = CLAY_RENDER_COMMAND_TYPE_TEXT, + [CLAY__LAYOUT_ELEMENT_TYPE_CUSTOM] = CLAY_RENDER_COMMAND_TYPE_CUSTOM, +}; + +typedef enum __attribute__((__packed__)) { + 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; + +typedef struct +{ + Clay_FloatingAttachPointType element; + Clay_FloatingAttachPointType parent; +} Clay_FloatingAttachPoints; + +typedef struct { + Clay_LayoutAlignmentX x; + Clay_LayoutAlignmentY y; +} Clay_ChildAlignment; + +typedef struct { + float min; + float max; +} Clay_SizingMinMax; + +typedef struct { + union { + Clay_SizingMinMax sizeMinMax; + float sizePercent; + }; + Clay__SizingType type; +} Clay_SizingAxis; + +typedef struct { + Clay_SizingAxis width; + Clay_SizingAxis height; +} Clay_Sizing; + +typedef struct { + uint16_t x; + uint16_t y; +} Clay_Padding; + +typedef struct { + float topLeft; + float topRight; + float bottomLeft; + float bottomRight; +} Clay_CornerRadius; + +typedef struct { + Clay_Sizing sizing; + Clay_Padding padding; + uint16_t childGap; + Clay_LayoutDirection layoutDirection; + Clay_ChildAlignment childAlignment; +} Clay_LayoutConfig; + +Clay_LayoutConfig CLAY_LAYOUT_DEFAULT = (Clay_LayoutConfig){}; + +// __GENERATED__ template array_define,array_add TYPE=Clay_LayoutConfig NAME=Clay_LayoutConfigArray DEFAULT_VALUE=&CLAY_LAYOUT_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_LayoutConfig *internalArray; +} Clay_LayoutConfigArray; + +Clay_LayoutConfigArray Clay_LayoutConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_LayoutConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutConfig), arena)}; +} +Clay_LayoutConfig *Clay_LayoutConfigArray_Add(Clay_LayoutConfigArray *array, Clay_LayoutConfig item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY_LAYOUT_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct { + Clay_Color color; + Clay_CornerRadius cornerRadius; + #ifdef CLAY_EXTEND_CONFIG_RECTANGLE + CLAY_EXTEND_CONFIG_RECTANGLE + #endif +} Clay_RectangleElementConfig; + +Clay_RectangleElementConfig CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT = (Clay_RectangleElementConfig){0}; + +// __GENERATED__ template array_define,array_add TYPE=Clay_RectangleElementConfig NAME=Clay_RectangleElementConfigArray DEFAULT_VALUE=&CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_RectangleElementConfig *internalArray; +} Clay_RectangleElementConfigArray; + +Clay_RectangleElementConfigArray Clay_RectangleElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_RectangleElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_RectangleElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_RectangleElementConfig), arena)}; +} +Clay_RectangleElementConfig *Clay_RectangleElementConfigArray_Add(Clay_RectangleElementConfigArray *array, Clay_RectangleElementConfig item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + Clay_Color textColor; + uint16_t fontId; + uint16_t fontSize; + uint16_t letterSpacing; + uint16_t lineSpacing; + #ifdef CLAY_EXTEND_CONFIG_TEXT + CLAY_EXTEND_CONFIG_TEXT + #endif +} Clay_TextElementConfig; + +Clay_TextElementConfig CLAY__TEXT_ELEMENT_CONFIG_DEFAULT = (Clay_TextElementConfig) {}; + +// __GENERATED__ template array_define,array_add TYPE=Clay_TextElementConfig NAME=Clay_TextElementConfigArray DEFAULT_VALUE=&CLAY__TEXT_ELEMENT_CONFIG_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_TextElementConfig *internalArray; +} Clay_TextElementConfigArray; + +Clay_TextElementConfigArray Clay_TextElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_TextElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_TextElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_TextElementConfig), arena)}; +} +Clay_TextElementConfig *Clay_TextElementConfigArray_Add(Clay_TextElementConfigArray *array, Clay_TextElementConfig item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__TEXT_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + void * imageData; + Clay_Dimensions sourceDimensions; + #ifdef CLAY_EXTEND_CONFIG_IMAGE + CLAY_EXTEND_CONFIG_IMAGE + #endif +} Clay_ImageElementConfig; + +Clay_ImageElementConfig CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT = (Clay_ImageElementConfig) {}; + +// __GENERATED__ template array_define,array_add TYPE=Clay_ImageElementConfig NAME=Clay_ImageElementConfigArray DEFAULT_VALUE=&CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_ImageElementConfig *internalArray; +} Clay_ImageElementConfigArray; + +Clay_ImageElementConfigArray Clay_ImageElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_ImageElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_ImageElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_ImageElementConfig), arena)}; +} +Clay_ImageElementConfig *Clay_ImageElementConfigArray_Add(Clay_ImageElementConfigArray *array, Clay_ImageElementConfig item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + Clay_Vector2 offset; + Clay_Dimensions expand; + uint16_t zIndex; + uint32_t parentId; + Clay_FloatingAttachPoints attachment; +} Clay_FloatingElementConfig; + +Clay_FloatingElementConfig CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT = (Clay_FloatingElementConfig) {}; + +// __GENERATED__ template array_define,array_add TYPE=Clay_FloatingElementConfig NAME=Clay_FloatingElementConfigArray DEFAULT_VALUE=&CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_FloatingElementConfig *internalArray; +} Clay_FloatingElementConfigArray; + +Clay_FloatingElementConfigArray Clay_FloatingElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_FloatingElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_FloatingElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_FloatingElementConfig), arena)}; +} +Clay_FloatingElementConfig *Clay_FloatingElementConfigArray_Add(Clay_FloatingElementConfigArray *array, Clay_FloatingElementConfig item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + #ifndef CLAY_EXTEND_CONFIG_CUSTOM + void* customData; + #else + CLAY_EXTEND_CONFIG_CUSTOM + #endif +} Clay_CustomElementConfig; + +Clay_CustomElementConfig CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT = (Clay_CustomElementConfig) {}; + +// __GENERATED__ template array_define,array_add TYPE=Clay_CustomElementConfig NAME=Clay_CustomElementConfigArray DEFAULT_VALUE=&CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_CustomElementConfig *internalArray; +} Clay_CustomElementConfigArray; + +Clay_CustomElementConfigArray Clay_CustomElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_CustomElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_CustomElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_CustomElementConfig), arena)}; +} +Clay_CustomElementConfig *Clay_CustomElementConfigArray_Add(Clay_CustomElementConfigArray *array, Clay_CustomElementConfig item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + bool horizontal; + bool vertical; +} Clay_ScrollContainerElementConfig; + +Clay_ScrollContainerElementConfig CLAY__SCROLL_CONTAINER_ELEMENT_CONFIG_DEFAULT = (Clay_ScrollContainerElementConfig ) {}; + +// __GENERATED__ template array_define,array_add TYPE=Clay_ScrollContainerElementConfig NAME=Clay_ScrollContainerElementConfigArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_ELEMENT_CONFIG_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_ScrollContainerElementConfig *internalArray; +} Clay_ScrollContainerElementConfigArray; + +Clay_ScrollContainerElementConfigArray Clay_ScrollContainerElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_ScrollContainerElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_ScrollContainerElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_ScrollContainerElementConfig), arena)}; +} +Clay_ScrollContainerElementConfig *Clay_ScrollContainerElementConfigArray_Add(Clay_ScrollContainerElementConfigArray *array, Clay_ScrollContainerElementConfig item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__SCROLL_CONTAINER_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + uint32_t width; + Clay_Color color; +} Clay_Border; + +typedef struct +{ + Clay_Border left; + Clay_Border right; + Clay_Border top; + Clay_Border bottom; + Clay_Border betweenChildren; + Clay_CornerRadius cornerRadius; +} Clay_BorderContainerElementConfig; + +Clay_BorderContainerElementConfig CLAY__BORDER_CONTAINER_ELEMENT_CONFIG_DEFAULT = (Clay_BorderContainerElementConfig ) {}; + +// __GENERATED__ template array_define,array_add TYPE=Clay_BorderContainerElementConfig NAME=Clay_BorderContainerElementConfigArray DEFAULT_VALUE=&CLAY__BORDER_CONTAINER_ELEMENT_CONFIG_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_BorderContainerElementConfig *internalArray; +} Clay_BorderContainerElementConfigArray; + +Clay_BorderContainerElementConfigArray Clay_BorderContainerElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_BorderContainerElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_BorderContainerElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_BorderContainerElementConfig), arena)}; +} +Clay_BorderContainerElementConfig *Clay_BorderContainerElementConfigArray_Add(Clay_BorderContainerElementConfigArray *array, Clay_BorderContainerElementConfig item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__BORDER_CONTAINER_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + struct t_Clay_LayoutElement **elements; + uint16_t length; +} Clay__LayoutElementChildren; + +typedef union +{ + Clay_RectangleElementConfig *rectangleElementConfig; + Clay_TextElementConfig *textElementConfig; + Clay_ImageElementConfig *imageElementConfig; + Clay_FloatingElementConfig *floatingElementConfig; + Clay_CustomElementConfig *customElementConfig; + Clay_ScrollContainerElementConfig *scrollElementConfig; + Clay_BorderContainerElementConfig *borderElementConfig; +} Clay_ElementConfigUnion; + +typedef struct t_Clay_LayoutElement +{ + #ifdef CLAY_DEBUG + Clay_String name; + #endif + union { + Clay__LayoutElementChildren children; + Clay_String text; + }; + Clay_Dimensions dimensions; + Clay_Dimensions minDimensions; + Clay_LayoutConfig *layoutConfig; + Clay_ElementConfigUnion elementConfig; + uint32_t id; + Clay__LayoutElementType elementType; +} Clay_LayoutElement; + +Clay_LayoutElement CLAY__LAYOUT_ELEMENT_DEFAULT = (Clay_LayoutElement) {}; + +// __GENERATED__ template array_define,array_add,array_get TYPE=Clay_LayoutElement NAME=Clay_LayoutElementArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_LayoutElement *internalArray; +} Clay_LayoutElementArray; + +Clay_LayoutElementArray Clay_LayoutElementArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_LayoutElementArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutElement *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutElement), arena)}; +} +Clay_LayoutElement *Clay_LayoutElementArray_Add(Clay_LayoutElementArray *array, Clay_LayoutElement item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__LAYOUT_ELEMENT_DEFAULT; +} +Clay_LayoutElement *Clay_LayoutElementArray_Get(Clay_LayoutElementArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_define,array_add,array_get,array_remove_swapback TYPE=Clay_LayoutElement* NAME=Clay__LayoutElementPointerArray DEFAULT_VALUE=CLAY__NULL +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_LayoutElement* *internalArray; +} Clay__LayoutElementPointerArray; + +Clay__LayoutElementPointerArray Clay__LayoutElementPointerArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay__LayoutElementPointerArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutElement* *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutElement*), arena)}; +} +Clay_LayoutElement* *Clay__LayoutElementPointerArray_Add(Clay__LayoutElementPointerArray *array, Clay_LayoutElement* item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return CLAY__NULL; +} +Clay_LayoutElement* *Clay__LayoutElementPointerArray_Get(Clay__LayoutElementPointerArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : CLAY__NULL; +} +Clay_LayoutElement* Clay__LayoutElementPointerArray_RemoveSwapback(Clay__LayoutElementPointerArray *array, int index) { + if (Clay__Array_RangeCheck(index, array->length)) { + array->length--; + Clay_LayoutElement* removed = array->internalArray[index]; + array->internalArray[index] = array->internalArray[array->length]; + return removed; + } + return CLAY__NULL; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + Clay_Rectangle boundingBox; + Clay_ElementConfigUnion config; + Clay_String text; // TODO I wish there was a way to avoid having to have this on every render command + uint32_t id; + Clay_RenderCommandType commandType; +} Clay_RenderCommand; + +Clay_RenderCommand CLAY__RENDER_COMMAND_DEFAULT = (Clay_RenderCommand) {}; + +// __GENERATED__ template array_define TYPE=Clay_RenderCommand NAME=Clay_RenderCommandArray +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_RenderCommand *internalArray; +} Clay_RenderCommandArray; + +Clay_RenderCommandArray Clay_RenderCommandArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay_RenderCommandArray){.capacity = capacity, .length = 0, .internalArray = (Clay_RenderCommand *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_RenderCommand), arena)}; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_add TYPE=Clay_RenderCommand NAME=Clay_RenderCommandArray DEFAULT_VALUE=&CLAY__RENDER_COMMAND_DEFAULT +#pragma region generated +Clay_RenderCommand *Clay_RenderCommandArray_Add(Clay_RenderCommandArray *array, Clay_RenderCommand item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__RENDER_COMMAND_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_get TYPE=Clay_RenderCommand NAME=Clay_RenderCommandArray DEFAULT_VALUE=&CLAY__RENDER_COMMAND_DEFAULT +#pragma region generated +Clay_RenderCommand *Clay_RenderCommandArray_Get(Clay_RenderCommandArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__RENDER_COMMAND_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + Clay_LayoutElement *layoutElement; + Clay_Rectangle boundingBox; + Clay_Dimensions contentSize; + Clay_Vector2 scrollOrigin; + Clay_Vector2 pointerOrigin; + Clay_Vector2 scrollMomentum; + Clay_Vector2 scrollPosition; + float momentumTime; + uint32_t elementId; + bool openThisFrame; + bool pointerScrollActive; +} Clay__ScrollContainerData; + +Clay__ScrollContainerData CLAY__SCROLL_CONTAINER_DEFAULT = (Clay__ScrollContainerData) {}; + +// __GENERATED__ template array_define TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay__ScrollContainerData *internalArray; +} Clay__ScrollContainerDataArray; + +Clay__ScrollContainerDataArray Clay__ScrollContainerDataArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay__ScrollContainerDataArray){.capacity = capacity, .length = 0, .internalArray = (Clay__ScrollContainerData *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__ScrollContainerData), arena)}; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_add TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_DEFAULT +#pragma region generated +Clay__ScrollContainerData *Clay__ScrollContainerDataArray_Add(Clay__ScrollContainerDataArray *array, Clay__ScrollContainerData item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__SCROLL_CONTAINER_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_get TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_DEFAULT +#pragma region generated +Clay__ScrollContainerData *Clay__ScrollContainerDataArray_Get(Clay__ScrollContainerDataArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__SCROLL_CONTAINER_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_remove_swapback TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray DEFAULT_VALUE=CLAY__SCROLL_CONTAINER_DEFAULT +#pragma region generated +Clay__ScrollContainerData Clay__ScrollContainerDataArray_RemoveSwapback(Clay__ScrollContainerDataArray *array, int index) { + if (Clay__Array_RangeCheck(index, array->length)) { + array->length--; + Clay__ScrollContainerData removed = array->internalArray[index]; + array->internalArray[index] = array->internalArray[array->length]; + return removed; + } + return CLAY__SCROLL_CONTAINER_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + Clay_Rectangle boundingBox; + uint32_t id; + Clay_LayoutElement* layoutElement; + int32_t nextIndex; +} Clay_LayoutElementHashMapItem; + +Clay_LayoutElementHashMapItem CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT = (Clay_LayoutElementHashMapItem) { .layoutElement = &CLAY__LAYOUT_ELEMENT_DEFAULT }; + +// __GENERATED__ template array_define,array_get,array_add,array_set TYPE=Clay_LayoutElementHashMapItem NAME=Clay__LayoutElementHashMapItemArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay_LayoutElementHashMapItem *internalArray; +} Clay__LayoutElementHashMapItemArray; + +Clay__LayoutElementHashMapItemArray Clay__LayoutElementHashMapItemArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay__LayoutElementHashMapItemArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutElementHashMapItem *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutElementHashMapItem), arena)}; +} +Clay_LayoutElementHashMapItem *Clay__LayoutElementHashMapItemArray_Get(Clay__LayoutElementHashMapItemArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT; +} +Clay_LayoutElementHashMapItem *Clay__LayoutElementHashMapItemArray_Add(Clay__LayoutElementHashMapItemArray *array, Clay_LayoutElementHashMapItem item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT; +} +void Clay__LayoutElementHashMapItemArray_Set(Clay__LayoutElementHashMapItemArray *array, int index, Clay_LayoutElementHashMapItem value) { + if (index < array->capacity && index >= 0) { + array->internalArray[index] = value; + array->length = index < array->length ? array->length : index + 1; + } else { + Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow.")); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + } +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + Clay_Dimensions dimensions; + uint32_t id; + int32_t nextIndex; +} Clay__MeasureTextCacheItem; + +Clay__MeasureTextCacheItem CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT = (Clay__MeasureTextCacheItem) { }; + +// __GENERATED__ template array_define,array_get,array_add,array_set TYPE=Clay__MeasureTextCacheItem NAME=Clay__MeasureTextCacheItemArray DEFAULT_VALUE=&CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay__MeasureTextCacheItem *internalArray; +} Clay__MeasureTextCacheItemArray; + +Clay__MeasureTextCacheItemArray Clay__MeasureTextCacheItemArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay__MeasureTextCacheItemArray){.capacity = capacity, .length = 0, .internalArray = (Clay__MeasureTextCacheItem *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__MeasureTextCacheItem), arena)}; +} +Clay__MeasureTextCacheItem *Clay__MeasureTextCacheItemArray_Get(Clay__MeasureTextCacheItemArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT; +} +Clay__MeasureTextCacheItem *Clay__MeasureTextCacheItemArray_Add(Clay__MeasureTextCacheItemArray *array, Clay__MeasureTextCacheItem item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT; +} +void Clay__MeasureTextCacheItemArray_Set(Clay__MeasureTextCacheItemArray *array, int index, Clay__MeasureTextCacheItem value) { + if (index < array->capacity && index >= 0) { + array->internalArray[index] = value; + array->length = index < array->length ? array->length : index + 1; + } else { + Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow.")); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + } +} +#pragma endregion +// __GENERATED__ template + +int32_t CLAY__INDEX_ARRAY_DEFAULT_VALUE = -1; + +// __GENERATED__ template array_define,array_get,array_add,array_set TYPE=int32_t NAME=Clay__int32_tArray DEFAULT_VALUE=&CLAY__INDEX_ARRAY_DEFAULT_VALUE +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + int32_t *internalArray; +} Clay__int32_tArray; + +Clay__int32_tArray Clay__int32_tArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay__int32_tArray){.capacity = capacity, .length = 0, .internalArray = (int32_t *)Clay__Array_Allocate_Arena(capacity, sizeof(int32_t), arena)}; +} +int32_t *Clay__int32_tArray_Get(Clay__int32_tArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__INDEX_ARRAY_DEFAULT_VALUE; +} +int32_t *Clay__int32_tArray_Add(Clay__int32_tArray *array, int32_t item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__INDEX_ARRAY_DEFAULT_VALUE; +} +void Clay__int32_tArray_Set(Clay__int32_tArray *array, int index, int32_t value) { + if (index < array->capacity && index >= 0) { + array->internalArray[index] = value; + array->length = index < array->length ? array->length : index + 1; + } else { + Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow.")); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + } +} +#pragma endregion +// __GENERATED__ template + +Clay_LayoutElement *Clay__openLayoutElement = CLAY__NULL; + +typedef struct +{ + Clay_LayoutElement *layoutElement; + Clay_Vector2 position; + Clay_Vector2 nextChildOffset; +} Clay__LayoutElementTreeNode; + +Clay__LayoutElementTreeNode CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT = (Clay__LayoutElementTreeNode) {}; + +// __GENERATED__ template array_define,array_add,array_get TYPE=Clay__LayoutElementTreeNode NAME=Clay__LayoutElementTreeNodeArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay__LayoutElementTreeNode *internalArray; +} Clay__LayoutElementTreeNodeArray; + +Clay__LayoutElementTreeNodeArray Clay__LayoutElementTreeNodeArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay__LayoutElementTreeNodeArray){.capacity = capacity, .length = 0, .internalArray = (Clay__LayoutElementTreeNode *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__LayoutElementTreeNode), arena)}; +} +Clay__LayoutElementTreeNode *Clay__LayoutElementTreeNodeArray_Add(Clay__LayoutElementTreeNodeArray *array, Clay__LayoutElementTreeNode item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT; +} +Clay__LayoutElementTreeNode *Clay__LayoutElementTreeNodeArray_Get(Clay__LayoutElementTreeNodeArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +typedef struct +{ + Clay_LayoutElement *layoutElement; + 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 + uint32_t zIndex; +} Clay__LayoutElementTreeRoot; + +Clay__LayoutElementTreeRoot CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT = (Clay__LayoutElementTreeRoot) {}; + +// __GENERATED__ template array_define,array_add,array_get TYPE=Clay__LayoutElementTreeRoot NAME=Clay__LayoutElementTreeRootArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT +#pragma region generated +typedef struct +{ + uint32_t capacity; + uint32_t length; + Clay__LayoutElementTreeRoot *internalArray; +} Clay__LayoutElementTreeRootArray; + +Clay__LayoutElementTreeRootArray Clay__LayoutElementTreeRootArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay__LayoutElementTreeRootArray){.capacity = capacity, .length = 0, .internalArray = (Clay__LayoutElementTreeRoot *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__LayoutElementTreeRoot), arena)}; +} +Clay__LayoutElementTreeRoot *Clay__LayoutElementTreeRootArray_Add(Clay__LayoutElementTreeRootArray *array, Clay__LayoutElementTreeRoot item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT; +} +Clay__LayoutElementTreeRoot *Clay__LayoutElementTreeRootArray_Get(Clay__LayoutElementTreeRootArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_Vector2 Clay__pointerPosition = (Clay_Vector2) { -1, -1 }; +uint64_t Clay__arenaResetOffset = 0; +Clay_Arena Clay__internalArena; +// Layout Elements / Render Commands +Clay_LayoutElementArray Clay__layoutElements; +Clay_RenderCommandArray Clay__renderCommands; +Clay__LayoutElementPointerArray Clay__openLayoutElementStack; +Clay__LayoutElementPointerArray Clay__layoutElementChildren; +Clay__LayoutElementPointerArray Clay__layoutElementChildrenBuffer; +Clay__LayoutElementPointerArray Clay__textElementPointers; +Clay__LayoutElementPointerArray Clay__imageElementPointers; +Clay__LayoutElementPointerArray Clay__layoutElementReusableBuffer; +// Configs +Clay_LayoutConfigArray Clay__layoutConfigs; +Clay_RectangleElementConfigArray Clay__rectangleElementConfigs; +Clay_TextElementConfigArray Clay__textElementConfigs; +Clay_ImageElementConfigArray Clay__imageElementConfigs; +Clay_FloatingElementConfigArray Clay__floatingElementConfigs; +Clay_ScrollContainerElementConfigArray Clay__scrollElementConfigs; +Clay_CustomElementConfigArray Clay__customElementConfigs; +Clay_BorderContainerElementConfigArray Clay__borderElementConfigs; +// Misc Data Structures +Clay__LayoutElementTreeNodeArray Clay__layoutElementTreeNodeArray1; +Clay__LayoutElementTreeRootArray Clay__layoutElementTreeRoots; +Clay__LayoutElementHashMapItemArray Clay__layoutElementsHashMapInternal; +Clay__int32_tArray Clay__layoutElementsHashMap; +Clay__MeasureTextCacheItemArray Clay__measureTextHashMapInternal; +Clay__int32_tArray Clay__measureTextHashMap; +Clay__int32_tArray Clay__openClipElementStack; +Clay__int32_tArray Clay__pointerOverIds; +Clay__ScrollContainerDataArray Clay__scrollContainerOffsets; +Clay__BoolArray Clay__treeNodeVisited; + +#if CLAY_WASM + __attribute__((import_module("clay"), import_name("measureTextFunction"))) Clay_Dimensions Clay__MeasureText(Clay_String *text, Clay_TextElementConfig *config); +#else + Clay_Dimensions (*Clay__MeasureText)(Clay_String *text, Clay_TextElementConfig *config); +#endif + +Clay_String LAST_HASH; + +uint32_t Clay__HashString(Clay_String key, const uint32_t offset) { + uint32_t hash = 0; + + for (int i = 0; i < key.length; i++) { + hash += key.chars[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += offset; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + #ifdef CLAY_DEBUG + LAST_HASH = key; + LAST_HASH.length = (int)offset; + #endif + return hash + 1; // Reserve the hash result of zero as "null id" +} + +uint32_t Clay__RehashWithNumber(uint32_t id, uint32_t number) { + id += number; + id += (id << 10); + id ^= (id >> 6); + + id += (id << 3); + id ^= (id >> 11); + id += (id << 15); + return id; +} + +uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *config) { + union { + float fontSize; + uint32_t bits; + } fontSizeBits = { .fontSize = config->fontSize }; + uint32_t hash = 0; + uint64_t pointerAsNumber = (uint64_t)text->chars; + + hash += pointerAsNumber; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += text->length; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += config->fontId; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += fontSizeBits.bits; + 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_Dimensions Clay__MeasureTextCached(Clay_String *text, Clay_TextElementConfig *config) { + if (text->length < 50) { + return Clay__MeasureText(text, config); + } + uint32_t id = Clay__HashTextWithConfig(text, config); + uint32_t hashBucket = id % Clay__measureTextHashMap.capacity; + int32_t elementIndexPrevious = 0; + int32_t elementIndex = Clay__measureTextHashMap.internalArray[hashBucket]; + while (elementIndex != 0) { + Clay__MeasureTextCacheItem *hashEntry = Clay__MeasureTextCacheItemArray_Get(&Clay__measureTextHashMapInternal, elementIndex); + if (hashEntry->id == id) { + return hashEntry->dimensions; + } + elementIndexPrevious = elementIndex; + elementIndex = hashEntry->nextIndex; + } + Clay_Dimensions measured = Clay__MeasureText(text, config); + if (elementIndexPrevious != 0) { + Clay__MeasureTextCacheItemArray_Get(&Clay__measureTextHashMapInternal, elementIndexPrevious)->nextIndex = (int32_t)Clay__measureTextHashMapInternal.length - 1; + } else { + Clay__measureTextHashMap.internalArray[hashBucket] = (int32_t)Clay__measureTextHashMapInternal.length - 1; + } + return measured; +} + +bool Clay__PointIsInsideRect(Clay_Vector2 point, Clay_Rectangle 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(uint32_t id, Clay_LayoutElement* layoutElement) { + Clay_LayoutElementHashMapItem item = (Clay_LayoutElementHashMapItem) { .id = id, .layoutElement = layoutElement, .nextIndex = -1 }; + uint32_t hashBucket = id % Clay__layoutElementsHashMap.capacity; + int32_t hashItemPrevious = -1; + int32_t hashItemIndex = Clay__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(&Clay__layoutElementsHashMapInternal, hashItemIndex); + if (hashItem->id == id) { // Collision - swap out linked list item + item.nextIndex = hashItem->nextIndex; + Clay__LayoutElementHashMapItemArray_Set(&Clay__layoutElementsHashMapInternal, hashItemIndex, item); + return Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, hashItemIndex); + } + hashItemPrevious = hashItemIndex; + hashItemIndex = hashItem->nextIndex; + } + if (hashItemPrevious != -1) { + Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, hashItemPrevious)->nextIndex = (int32_t)Clay__layoutElementsHashMapInternal.length; + } + Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Add(&Clay__layoutElementsHashMapInternal, item); + Clay__layoutElementsHashMap.internalArray[hashBucket] = (int32_t)Clay__layoutElementsHashMapInternal.length - 1; + return hashItem; +} + +Clay_LayoutElementHashMapItem *Clay__GetHashMapItem(uint32_t id) { + uint32_t hashBucket = id % Clay__layoutElementsHashMap.capacity; + int32_t elementIndex = Clay__layoutElementsHashMap.internalArray[hashBucket]; + while (elementIndex != -1) { + Clay_LayoutElementHashMapItem *hashEntry = Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, elementIndex); + if (hashEntry->id == id) { + return hashEntry; + } + elementIndex = hashEntry->nextIndex; + } + return CLAY__NULL; +} + +Clay_LayoutElement *Clay__OpenElementWithParent(uint32_t id, Clay__LayoutElementType commandType, Clay_LayoutConfig* layoutConfig, Clay_ElementConfigUnion elementConfig) { + Clay_LayoutElement layoutElement = (Clay_LayoutElement) { + #ifdef CLAY_DEBUG + .name = LAST_HASH, + #endif + .id = id, + .elementType = commandType, + .minDimensions = (Clay_Dimensions) { (float)layoutConfig->padding.x * 2, (float)layoutConfig->padding.y * 2 }, + .children = (Clay__LayoutElementChildren) { .length = 0 }, + .layoutConfig = layoutConfig, + .elementConfig = elementConfig, + }; + + if (layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { + layoutElement.dimensions.width = (float)layoutConfig->padding.x * 2; + layoutElement.minDimensions.width = CLAY__MAX(layoutElement.minDimensions.width, layoutConfig->sizing.width.sizeMinMax.min); + if (layoutConfig->sizing.width.sizeMinMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier + layoutConfig->sizing.width.sizeMinMax.max = CLAY__MAXFLOAT; + } + } + if (layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { + layoutElement.dimensions.height = (float)layoutConfig->padding.y * 2; + layoutElement.minDimensions.height = CLAY__MAX(layoutElement.minDimensions.height, layoutConfig->sizing.height.sizeMinMax.min); + if (layoutConfig->sizing.height.sizeMinMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier + layoutConfig->sizing.height.sizeMinMax.max = CLAY__MAXFLOAT; + } + } + + Clay__openLayoutElement = Clay_LayoutElementArray_Add(&Clay__layoutElements, layoutElement); + Clay__LayoutElementPointerArray_Add(&Clay__openLayoutElementStack, Clay__openLayoutElement); + Clay__AddHashMapItem(id, Clay__openLayoutElement); + return Clay__openLayoutElement; +} + +Clay_LayoutElement *Clay__OpenElement(uint32_t id, Clay__LayoutElementType commandType, Clay_LayoutConfig *layoutConfig, Clay_ElementConfigUnion elementConfig) { + Clay__openLayoutElement->children.length++; + Clay_LayoutElement *element = Clay__OpenElementWithParent(id, commandType, layoutConfig, elementConfig); + Clay__LayoutElementPointerArray_Add(&Clay__layoutElementChildrenBuffer, element); + return element; +} + +void Clay__OpenContainerElement(int id, Clay_LayoutConfig *layoutConfig) { + Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_CONTAINER, layoutConfig, (Clay_ElementConfigUnion){ CLAY__NULL }); +} + +void Clay__OpenRectangleElement(int id, Clay_LayoutConfig *layoutConfig, Clay_RectangleElementConfig *rectangleConfig) { + Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_RECTANGLE, layoutConfig, (Clay_ElementConfigUnion) { .rectangleElementConfig = rectangleConfig }); +} + +void Clay__OpenImageContainerElement(int id, Clay_LayoutConfig *layoutConfig, Clay_ImageElementConfig *imageConfig) { + Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_IMAGE, layoutConfig, (Clay_ElementConfigUnion) { .imageElementConfig = imageConfig }); + Clay__LayoutElementPointerArray_Add(&Clay__imageElementPointers, Clay__openLayoutElement); +} + +void Clay__OpenBorderContainerElement(int id, Clay_LayoutConfig *layoutConfig, Clay_BorderContainerElementConfig *borderConfig) { + Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_BORDER_CONTAINER, layoutConfig, (Clay_ElementConfigUnion){ .borderElementConfig = borderConfig }); +} + +void Clay__OpenCustomElement(uint32_t id, Clay_LayoutConfig *layoutConfig, Clay_CustomElementConfig *customElementConfig) { + Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_CUSTOM, layoutConfig, (Clay_ElementConfigUnion) { .customElementConfig = customElementConfig }); +} + +Clay_LayoutElement *Clay__OpenScrollElement(uint32_t id, Clay_LayoutConfig *layoutConfig, Clay_ScrollContainerElementConfig *scrollConfig) { + Clay_LayoutElement *scrollElement = Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER, layoutConfig, (Clay_ElementConfigUnion){ .scrollElementConfig = scrollConfig }); + Clay__int32_tArray_Add(&Clay__openClipElementStack, (int)scrollElement->id); + Clay__ScrollContainerData *scrollOffset = CLAY__NULL; + for (int i = 0; i < Clay__scrollContainerOffsets.length; i++) { + Clay__ScrollContainerData *mapping = Clay__ScrollContainerDataArray_Get(&Clay__scrollContainerOffsets, i); + if (id == mapping->elementId) { + scrollOffset = mapping; + scrollOffset->layoutElement = scrollElement; + scrollOffset->openThisFrame = true; + } + } + if (!scrollOffset) { + Clay__ScrollContainerDataArray_Add(&Clay__scrollContainerOffsets, (Clay__ScrollContainerData){.elementId = id, .layoutElement = scrollElement, .scrollOrigin = {-1,-1}, .openThisFrame = true}); + } + return scrollElement; +} + +Clay_LayoutElement *Clay__OpenFloatingContainerElement(uint32_t id, Clay_LayoutConfig *layoutConfig, Clay_FloatingElementConfig *floatingElementConfig) { + Clay_LayoutElement *parent = Clay__openLayoutElement; + if (floatingElementConfig->parentId > 0) { + Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingElementConfig->parentId); + if (!parentItem) { + Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Clay Warning: Couldn't find parent container to attach floating container to.")); + } else { + parent = parentItem->layoutElement; + } + } + Clay__OpenElementWithParent(id, CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER, layoutConfig, (Clay_ElementConfigUnion) { .floatingElementConfig = floatingElementConfig }); + Clay__LayoutElementTreeRootArray_Add(&Clay__layoutElementTreeRoots, (Clay__LayoutElementTreeRoot) { + .layoutElement = Clay__openLayoutElement, + .parentId = parent->id, + .zIndex = floatingElementConfig->zIndex, + .clipElementId = Clay__openClipElementStack.length > 0 ? (uint32_t)*Clay__int32_tArray_Get(&Clay__openClipElementStack, (int)Clay__openClipElementStack.length - 1) : 0, + }); + return Clay__openLayoutElement; +} + +void Clay__AttachContainerChildren() { + Clay_LayoutConfig *layoutConfig = Clay__openLayoutElement->layoutConfig; + Clay__openLayoutElement->children.elements = &Clay__layoutElementChildren.internalArray[Clay__layoutElementChildren.length]; + + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + for (int i = 0; i < Clay__openLayoutElement->children.length; i++) { + Clay_LayoutElement *child = *Clay__LayoutElementPointerArray_Get(&Clay__layoutElementChildrenBuffer, (int)Clay__layoutElementChildrenBuffer.length - Clay__openLayoutElement->children.length + i); + Clay__openLayoutElement->dimensions.width += child->dimensions.width; + Clay__openLayoutElement->dimensions.height = CLAY__MAX(Clay__openLayoutElement->dimensions.height, child->dimensions.height + layoutConfig->padding.y * 2); + // Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents + if (Clay__openLayoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || !Clay__openLayoutElement->elementConfig.scrollElementConfig->horizontal) { + Clay__openLayoutElement->minDimensions.width += child->minDimensions.width; + } + if (Clay__openLayoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || !Clay__openLayoutElement->elementConfig.scrollElementConfig->vertical) { + Clay__openLayoutElement->minDimensions.height = CLAY__MAX(Clay__openLayoutElement->minDimensions.height, child->minDimensions.height + layoutConfig->padding.y * 2); + } + Clay__LayoutElementPointerArray_Add(&Clay__layoutElementChildren, child); + } + float childGap = (float)(CLAY__MAX(Clay__openLayoutElement->children.length - 1, 0) * layoutConfig->childGap); + Clay__openLayoutElement->dimensions.width += childGap; + Clay__openLayoutElement->minDimensions.width += childGap; + } + else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { + for (int i = 0; i < Clay__openLayoutElement->children.length; i++) { + Clay_LayoutElement *child = *Clay__LayoutElementPointerArray_Get(&Clay__layoutElementChildrenBuffer, (int)Clay__layoutElementChildrenBuffer.length - Clay__openLayoutElement->children.length + i); + Clay__openLayoutElement->dimensions.height += child->dimensions.height; + Clay__openLayoutElement->dimensions.width = CLAY__MAX(Clay__openLayoutElement->dimensions.width, child->dimensions.width + layoutConfig->padding.x * 2); + // Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents + if (Clay__openLayoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || !Clay__openLayoutElement->elementConfig.scrollElementConfig->vertical) { + Clay__openLayoutElement->minDimensions.height += child->minDimensions.height; + } + if (Clay__openLayoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || !Clay__openLayoutElement->elementConfig.scrollElementConfig->horizontal) { + Clay__openLayoutElement->minDimensions.width = CLAY__MAX(Clay__openLayoutElement->minDimensions.width, child->minDimensions.width + layoutConfig->padding.x * 2); + } + Clay__LayoutElementPointerArray_Add(&Clay__layoutElementChildren, child); + } + float childGap = (float)(CLAY__MAX(Clay__openLayoutElement->children.length - 1, 0) * layoutConfig->childGap); + Clay__openLayoutElement->dimensions.height += childGap; + Clay__openLayoutElement->minDimensions.height += childGap; + } + + Clay__layoutElementChildrenBuffer.length -= Clay__openLayoutElement->children.length; +} + +void Clay__CloseElement() { + Clay_LayoutConfig *layoutConfig = Clay__openLayoutElement->layoutConfig; + + if (layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { + // TODO I think minsize has already been applied by this point so no need to do it again + Clay__openLayoutElement->dimensions.width = CLAY__MIN(CLAY__MAX(Clay__openLayoutElement->dimensions.width, layoutConfig->sizing.width.sizeMinMax.min), layoutConfig->sizing.width.sizeMinMax.max); + } else { + Clay__openLayoutElement->dimensions.width = 0; + } + if (layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { + Clay__openLayoutElement->dimensions.height = CLAY__MIN(CLAY__MAX(Clay__openLayoutElement->dimensions.height, layoutConfig->sizing.height.sizeMinMax.min), layoutConfig->sizing.height.sizeMinMax.max); + } else { + Clay__openLayoutElement->dimensions.height = 0; + } + + Clay__LayoutElementPointerArray_RemoveSwapback(&Clay__openLayoutElementStack, (int)Clay__openLayoutElementStack.length - 1); + Clay__openLayoutElement = *Clay__LayoutElementPointerArray_Get(&Clay__openLayoutElementStack, (int)Clay__openLayoutElementStack.length - 1); +} + +void Clay__OpenTextElement(int id, Clay_String text, Clay_TextElementConfig *textConfig) { + Clay_LayoutElement *internalElement = Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_TEXT, &CLAY_LAYOUT_DEFAULT, (Clay_ElementConfigUnion) { .textElementConfig = textConfig }); + Clay_Dimensions textMeasured = Clay__MeasureTextCached(&text, textConfig); + internalElement->dimensions.width = textMeasured.width; + internalElement->dimensions.height = textMeasured.height; + internalElement->text = text; + internalElement->minDimensions = (Clay_Dimensions) { .width = textMeasured.height, .height = textMeasured.height }; // TODO not sure this is the best way to decide min width for text + Clay__LayoutElementPointerArray_Add(&Clay__textElementPointers, internalElement); + Clay__CloseElement(); +} + +void Clay__CloseContainerElement() { + Clay__AttachContainerChildren(); + Clay__CloseElement(); +} + +void Clay__CloseScrollContainerElement() { + Clay__openClipElementStack.length--; + Clay__CloseContainerElement(); +} + +void Clay__CloseFloatingContainer() { + Clay__AttachContainerChildren(); + Clay__CloseElement(); +} + +void Clay__InitializeEphemeralMemory(Clay_Arena *arena) { + // Ephemeral Memory - reset every frame + Clay__internalArena.nextAllocation = Clay__arenaResetOffset; + + Clay__layoutElementChildrenBuffer = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__layoutElements = Clay_LayoutElementArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay_warnings = Clay_StringArray_Allocate_Arena(100, arena); + + Clay__layoutConfigs = Clay_LayoutConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__rectangleElementConfigs = Clay_RectangleElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__textElementConfigs = Clay_TextElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__imageElementConfigs = Clay_ImageElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__floatingElementConfigs = Clay_FloatingElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__scrollElementConfigs = Clay_ScrollContainerElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__customElementConfigs = Clay_CustomElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__borderElementConfigs = Clay_BorderContainerElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + + Clay__layoutElementTreeNodeArray1 = Clay__LayoutElementTreeNodeArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__layoutElementTreeRoots = Clay__LayoutElementTreeRootArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__layoutElementChildren = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__openLayoutElementStack = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__textElementPointers = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__imageElementPointers = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__layoutElementReusableBuffer = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__renderCommands = Clay_RenderCommandArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__treeNodeVisited = Clay__BoolArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__treeNodeVisited.length = Clay__treeNodeVisited.capacity; // This array is accessed directly rather than behaving as a list + Clay__openClipElementStack = Clay__int32_tArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); +} + +void Clay__InitializePersistentMemory(Clay_Arena *arena) { + Clay__scrollContainerOffsets = Clay__ScrollContainerDataArray_Allocate_Arena(10, arena); + Clay__layoutElementsHashMapInternal = Clay__LayoutElementHashMapItemArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__layoutElementsHashMap = Clay__int32_tArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__measureTextHashMapInternal = Clay__MeasureTextCacheItemArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__measureTextHashMap = Clay__int32_tArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__pointerOverIds = Clay__int32_tArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); + Clay__arenaResetOffset = arena->nextAllocation; +} + + +typedef enum +{ + CLAY__SIZE_DISTRIBUTION_TYPE_SCROLL_CONTAINER, + CLAY__SIZE_DISTRIBUTION_TYPE_RESIZEABLE_CONTAINER, + CLAY__SIZE_DISTRIBUTION_TYPE_GROW_CONTAINER, +} Clay__SizeDistributionType; + +// Because of the max and min sizing properties, we can't predict ahead of time how (or if) all the excess width +// will actually be distributed. So we keep looping until either all the excess width is distributed or +// we have exhausted all our containers that can change size along this axis +float Clay__DistributeSizeAmongChildren(bool xAxis, float sizeToDistribute, Clay__LayoutElementPointerArray resizableContainerBuffer, Clay__SizeDistributionType distributionType) { + Clay__LayoutElementPointerArray backBuffer = Clay__layoutElementReusableBuffer; + backBuffer.length = 0; + + Clay__LayoutElementPointerArray remainingElements = resizableContainerBuffer; + float totalDistributedSize; + while (sizeToDistribute != 0 && remainingElements.length > 0) { + totalDistributedSize = 0; + for (int childOffset = 0; childOffset < remainingElements.length; childOffset++) { + Clay_LayoutElement *childElement = *Clay__LayoutElementPointerArray_Get(&remainingElements, childOffset); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; + float childMinSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height; + + if ((sizeToDistribute < 0 && *childSize == childSizing.sizeMinMax.min) || (sizeToDistribute > 0 && *childSize == childSizing.sizeMinMax.max)) { + continue; + } + + if (!xAxis && childElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_IMAGE) { + continue; // Currently, we don't support squishing aspect ratio images on their Y axis as it would break ratio + } + + switch (distributionType) { + case CLAY__SIZE_DISTRIBUTION_TYPE_RESIZEABLE_CONTAINER: break; + case CLAY__SIZE_DISTRIBUTION_TYPE_GROW_CONTAINER: if (childSizing.type != CLAY__SIZING_TYPE_GROW) { continue; } break; + case CLAY__SIZE_DISTRIBUTION_TYPE_SCROLL_CONTAINER: if ((childElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || (xAxis && !childElement->elementConfig.scrollElementConfig->horizontal) || (!xAxis && !childElement->elementConfig.scrollElementConfig->vertical))) { continue; } break; + } + + float dividedSize = sizeToDistribute / (float)(remainingElements.length - childOffset); + float oldChildSize = *childSize; + *childSize = CLAY__MAX(CLAY__MAX(CLAY__MIN(childSizing.sizeMinMax.max, *childSize + dividedSize), childSizing.sizeMinMax.min), childMinSize); + float diff = *childSize - oldChildSize; + if (diff != 0) { + Clay__LayoutElementPointerArray_Add(&backBuffer, childElement); + } + sizeToDistribute -= diff; + totalDistributedSize += diff; + } + if (totalDistributedSize == 0) { + break; + } + // Flip the buffers + Clay__LayoutElementPointerArray temp = remainingElements; + remainingElements = backBuffer; + backBuffer = temp; + } + return sizeToDistribute; +} + +void Clay__SizeContainersAlongAxis(bool xAxis) { + Clay__LayoutElementPointerArray bfsBuffer = Clay__layoutElementChildrenBuffer; + Clay__LayoutElementPointerArray resizableContainerBuffer = Clay__openLayoutElementStack; + for (int rootIndex = 0; rootIndex < Clay__layoutElementTreeRoots.length; ++rootIndex) { + bfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex); + Clay_LayoutElement *rootElement = root->layoutElement; + Clay__LayoutElementPointerArray_Add(&bfsBuffer, root->layoutElement); + rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->layoutConfig->sizing.width.sizeMinMax.min), rootElement->layoutConfig->sizing.width.sizeMinMax.max); + rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->layoutConfig->sizing.height.sizeMinMax.min), rootElement->layoutConfig->sizing.height.sizeMinMax.max); + + for (int i = 0; i < bfsBuffer.length; ++i) { + Clay_LayoutElement *parent = *Clay__LayoutElementPointerArray_Get(&bfsBuffer, i); + Clay_LayoutConfig *parentStyleConfig = parent->layoutConfig; + float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height; + float parentPadding = (float)(xAxis ? parent->layoutConfig->padding.x : parent->layoutConfig->padding.y); + float innerContentSize = 0, totalPaddingAndChildGaps = parentPadding * 2; + bool sizingAlongAxis = (xAxis && parentStyleConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentStyleConfig->layoutDirection == CLAY_TOP_TO_BOTTOM); + resizableContainerBuffer.length = 0; + float parentChildGap = parentStyleConfig->childGap; + + for (int childOffset = 0; childOffset < parent->children.length; childOffset++) { + Clay_LayoutElement *childElement = parent->children.elements[childOffset]; + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height; + + if (childElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_TEXT && childElement->children.length > 0) { + Clay__LayoutElementPointerArray_Add(&bfsBuffer, childElement); + } + + if (childSizing.type != CLAY__SIZING_TYPE_PERCENT) { + Clay__LayoutElementPointerArray_Add(&resizableContainerBuffer, childElement); + } + + if (sizingAlongAxis) { + innerContentSize += (childSizing.type == CLAY__SIZING_TYPE_PERCENT ? 0 : childSize); + 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 (int childOffset = 0; childOffset < parent->children.length; childOffset++) { + Clay_LayoutElement *childElement = parent->children.elements[childOffset]; + 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.sizePercent; + if (sizingAlongAxis) { + innerContentSize += *childSize; + 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); + } + } + } + + if (sizingAlongAxis) { + float sizeToDistribute = parentSize - parentPadding * 2 - innerContentSize; + // If the content is too large, compress the children as much as possible + if (sizeToDistribute < 0) { + // If the parent can scroll in the axis direction in this direction, just leave the children alone + if (parent->elementType == CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER) { + if (((xAxis && parent->elementConfig.scrollElementConfig->horizontal) || (!xAxis && parent->elementConfig.scrollElementConfig->vertical))) { + continue; + } + } + // Scrolling containers preferentially compress before others + sizeToDistribute = Clay__DistributeSizeAmongChildren(xAxis, sizeToDistribute, resizableContainerBuffer, CLAY__SIZE_DISTRIBUTION_TYPE_SCROLL_CONTAINER); + + // If there is still height to make up, remove it from all containers that haven't hit their minimum size + if (sizeToDistribute < 0) { + Clay__DistributeSizeAmongChildren(xAxis, sizeToDistribute, resizableContainerBuffer, CLAY__SIZE_DISTRIBUTION_TYPE_RESIZEABLE_CONTAINER); + } + // The content is too small, allow SIZING_GROW containers to expand + } else { + Clay__DistributeSizeAmongChildren(xAxis, sizeToDistribute, resizableContainerBuffer, CLAY__SIZE_DISTRIBUTION_TYPE_GROW_CONTAINER); + } + // Sizing along the non layout axis ("off axis") + } else { + for (int childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) { + Clay_LayoutElement *childElement = *Clay__LayoutElementPointerArray_Get(&resizableContainerBuffer, childOffset); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; + + if (!xAxis && childElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_IMAGE) { + continue; // Currently we don't support resizing aspect ratio images on the Y axis because it would break the ratio + } + + // If we're laying out the children of a scroll panel, grow containers expand to the height of the inner content, not the outer container + float maxSize = parentSize - parentPadding * 2; + if (parent->elementType == CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER && ((xAxis && parent->elementConfig.scrollElementConfig->horizontal) || (!xAxis && parent->elementConfig.scrollElementConfig->vertical))) { + maxSize = CLAY__MAX(maxSize, innerContentSize); + } + if (childSizing.type == CLAY__SIZING_TYPE_FIT) { + *childSize = CLAY__MAX(childSizing.sizeMinMax.min, CLAY__MIN(*childSize, maxSize)); + } else if (childSizing.type == CLAY__SIZING_TYPE_GROW) { + *childSize = CLAY__MIN(maxSize, childSizing.sizeMinMax.max); + } + } + } + } + } +} + +void Clay__CalculateFinalLayout(int screenWidth, int screenHeight) { + // layoutElementsHashMap has non-linear access pattern so just resetting .length won't zero out the data. + // Need to zero it all out here + for (int i = 0; i < Clay__layoutElementsHashMap.capacity; ++i) { + Clay__layoutElementsHashMap.internalArray[i] = -1; + } + Clay__layoutElementsHashMapInternal.length = 0; + + // Calculate sizing along the X axis + Clay__SizeContainersAlongAxis(true); + + // Wrap text + uint32_t originalTextLayoutElementDataLength = Clay__textElementPointers.length; + for (int i = 0; i < originalTextLayoutElementDataLength; ++i) { + Clay_LayoutElement *containerElement = *Clay__LayoutElementPointerArray_Get(&Clay__textElementPointers, i); + Clay_String text = containerElement->text; + Clay_TextElementConfig *textConfig = containerElement->elementConfig.textElementConfig; + containerElement->elementType = CLAY__LAYOUT_ELEMENT_TYPE_CONTAINER; + // Clone the style config to prevent pollution of other elements that share this config + containerElement->layoutConfig = Clay_LayoutConfigArray_Add(&Clay__layoutConfigs, *containerElement->layoutConfig); + containerElement->layoutConfig->layoutDirection = CLAY_TOP_TO_BOTTOM; + containerElement->layoutConfig->childGap = textConfig->lineSpacing; + containerElement->dimensions.height = 0; + float fontSize = containerElement->elementConfig.textElementConfig->fontSize; + int lineStartIndex = 0; + int wordStartIndex = 0; + int wordEndIndex = 0; + containerElement->children = (Clay__LayoutElementChildren) { // Note: this overwrites the text property + .length = 0, + .elements = &Clay__layoutElementChildren.internalArray[Clay__layoutElementChildren.length] + }; + Clay_Dimensions lineDimensions = (Clay_Dimensions){}; + float spaceWidth = Clay__MeasureText(&CLAY__SPACECHAR, textConfig).width; // todo may as well cache it somewhere + while (wordStartIndex < text.length) { + if (text.chars[wordEndIndex] == ' ' || text.chars[wordEndIndex] == '\n' || wordEndIndex == text.length) { + Clay_String stringToRender = (Clay_String) { .length = wordEndIndex - lineStartIndex, .chars = text.chars + lineStartIndex }; + Clay_String wordToMeasure = (Clay_String) { .length = wordEndIndex - wordStartIndex, .chars = text.chars + wordStartIndex }; + // Clip off trailing spaces and newline characters + Clay_Dimensions wordDimensions = Clay__MeasureTextCached(&wordToMeasure, textConfig); + lineDimensions.width = lineDimensions.width + wordDimensions.width + spaceWidth; + lineDimensions.height = wordDimensions.height; + bool isOverlappingBoundaries = (lineDimensions.width - spaceWidth) > containerElement->dimensions.width + 0.01f; // Epsilon for floating point inaccuracy of adding components + // Need to wrap + if (isOverlappingBoundaries) { + lineDimensions.width -= spaceWidth; + // We can wrap at the most recent word start + if (wordStartIndex != lineStartIndex) { + stringToRender = (Clay_String) { .length = wordStartIndex - lineStartIndex - 1, .chars = text.chars + lineStartIndex }; + lineDimensions.width -= (wordDimensions.width + spaceWidth); + lineStartIndex = wordStartIndex; + wordStartIndex = lineStartIndex; + wordEndIndex = lineStartIndex; + containerElement->dimensions.width = CLAY__MAX(containerElement->dimensions.width, lineDimensions.width); + // The single word is larger than the entire container - just render it in place + } else { + lineStartIndex = wordEndIndex + 1; + wordStartIndex = lineStartIndex; + wordEndIndex = lineStartIndex; + containerElement->dimensions.width = CLAY__MAX(containerElement->dimensions.width, lineDimensions.width); + } + // If we're at a space character and the current phrase fits, just keep going + } else if (text.chars[wordEndIndex] == ' ') { + wordStartIndex = wordEndIndex + 1; + wordEndIndex = wordStartIndex; + continue; + // Newline or end of string + } else { + lineStartIndex = wordEndIndex + 1; + wordStartIndex = lineStartIndex; + wordEndIndex = lineStartIndex; + } + Clay_LayoutElement *newTextLayoutElement = Clay_LayoutElementArray_Add(&Clay__layoutElements, (Clay_LayoutElement) { + .id = Clay__RehashWithNumber(containerElement->id, containerElement->children.length), + .elementType = CLAY__LAYOUT_ELEMENT_TYPE_TEXT, + .text = stringToRender, + .layoutConfig = &CLAY_LAYOUT_DEFAULT, + .elementConfig.textElementConfig = containerElement->elementConfig.textElementConfig, + .dimensions = { lineDimensions.width, lineDimensions.height }, + }); + containerElement->dimensions.height += lineDimensions.height + (float)(containerElement->children.length > 0 ? textConfig->lineSpacing : 0); + containerElement->children.length++; + lineDimensions = (Clay_Dimensions) {}; + Clay__LayoutElementPointerArray_Add(&Clay__layoutElementChildren, newTextLayoutElement); + } else { + // In the middle of a word + wordEndIndex++; + } + } + } + + // Scale vertical image heights according to aspect ratio + for (int i = 0; i < Clay__imageElementPointers.length; ++i) { + Clay_LayoutElement* imageElement = *Clay__LayoutElementPointerArray_Get(&Clay__imageElementPointers, i); + Clay_ImageElementConfig *config = imageElement->elementConfig.imageElementConfig; + imageElement->dimensions.height = (config->sourceDimensions.height / CLAY__MAX(config->sourceDimensions.width, 1)) * imageElement->dimensions.width; + } + + // Propagate effect of text wrapping, image aspect scaling etc. on height of parents + Clay__LayoutElementTreeNodeArray dfsBuffer = Clay__layoutElementTreeNodeArray1; + dfsBuffer.length = 0; + for (int i = 0; i < Clay__layoutElementTreeRoots.length; ++i) { + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, i); + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, (Clay__LayoutElementTreeNode) { .layoutElement = root->layoutElement }); + } + Clay__treeNodeVisited.internalArray[0] = false; + while (dfsBuffer.length > 0) { + Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); + Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; + if (!Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + Clay__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 (currentElement->children.length == 0 || (currentElement->children.length > 0 && currentElement->children.elements[0]->elementType == CLAY__LAYOUT_ELEMENT_TYPE_TEXT)) { + 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 (int i = 0; i < currentElement->children.length; i++) { + Clay__treeNodeVisited.internalArray[dfsBuffer.length] = false; + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, (Clay__LayoutElementTreeNode) { .layoutElement = currentElement->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->sizing.height.type == CLAY__SIZING_TYPE_PERCENT) { + continue; + } + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + // Resize any parent containers that have grown in height along their non layout axis + for (int j = 0; j < currentElement->children.length; ++j) { + Clay_LayoutElement *childElement = currentElement->children.elements[j]; + float childHeightWithPadding = CLAY__MAX(childElement->dimensions.height + layoutConfig->padding.y * 2, currentElement->dimensions.height); + currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(childHeightWithPadding, layoutConfig->sizing.height.sizeMinMax.min), layoutConfig->sizing.height.sizeMinMax.max); + } + } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { + // Resizing along the layout axis + float contentHeight = (float)layoutConfig->padding.y * 2; + for (int j = 0; j < currentElement->children.length; ++j) { + Clay_LayoutElement *childElement = currentElement->children.elements[j]; + contentHeight += childElement->dimensions.height; + } + contentHeight += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap); + currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(contentHeight, layoutConfig->sizing.height.sizeMinMax.min), layoutConfig->sizing.height.sizeMinMax.max); + } + } + + // Calculate sizing along the Y axis + Clay__SizeContainersAlongAxis(false); + + // Calculate final positions and generate render commands + Clay__renderCommands.length = 0; + dfsBuffer.length = 0; + for (int rootIndex = 0; rootIndex < Clay__layoutElementTreeRoots.length; ++rootIndex) { + dfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex); + Clay_Vector2 rootPosition = (Clay_Vector2) {}; + Clay_LayoutElementHashMapItem *parentHashMapItem = Clay__GetHashMapItem(root->parentId); + // Position root floating containers + if (parentHashMapItem) { + Clay_FloatingElementConfig *config = root->layoutElement->elementConfig.floatingElementConfig; + Clay_Dimensions rootDimensions = root->layoutElement->dimensions; + Clay_Rectangle parentBoundingBox = parentHashMapItem->boundingBox; + // Set X position + Clay_Vector2 targetAttachPosition = (Clay_Vector2){}; + switch (config->attachment.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->attachment.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->attachment.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->attachment.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) { + Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) { + .id = Clay__RehashWithNumber(root->layoutElement->id, 10), // TODO need a better strategy for managing derived ids + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, + .boundingBox = clipHashMapItem->boundingBox, + }); + } + } + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, (Clay__LayoutElementTreeNode) { .layoutElement = root->layoutElement, .position = rootPosition, .nextChildOffset = (Clay_Vector2) { .x = (float)root->layoutElement->layoutConfig->padding.x, .y = (float)root->layoutElement->layoutConfig->padding.y } }); + + Clay__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 = {0}; + + // This will only be run a single time for each element in downwards DFS order + if (!Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + + Clay_Rectangle currentElementBoundingBox = (Clay_Rectangle) { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height }; + if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER) { + Clay_FloatingElementConfig *floatingElementConfig = currentElement->elementConfig.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__ScrollContainerData *scrollContainerData = CLAY__NULL; + // Apply scroll offsets to container + if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER) { + Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) { + .id = Clay__RehashWithNumber(currentElement->id, 10), + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, + .boundingBox = currentElementBoundingBox, + }); + + // 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 (int i = 0; i < Clay__scrollContainerOffsets.length; i++) { + Clay__ScrollContainerData *mapping = Clay__ScrollContainerDataArray_Get(&Clay__scrollContainerOffsets, i); + if (mapping->layoutElement == currentElement) { + scrollContainerData = mapping; + mapping->boundingBox = currentElementBoundingBox; + Clay_ScrollContainerElementConfig *config = mapping->layoutElement->elementConfig.scrollElementConfig; + if (config->horizontal) { + scrollOffset.x = mapping->scrollPosition.x; + } + if (config->vertical) { + scrollOffset.y = mapping->scrollPosition.y; + } + break; + } + } + } + + // Create the render command for this element + Clay_RenderCommand renderCommand = (Clay_RenderCommand) { + .id = currentElement->id, + .commandType = Clay__LayoutElementTypeToRenderCommandType[currentElement->elementType], + .boundingBox = currentElementBoundingBox, + .config = currentElement->elementConfig + }; + + Clay_LayoutElementHashMapItem *hashMapItem = Clay__AddHashMapItem(currentElement->id, currentElement); + if (hashMapItem) { + hashMapItem->boundingBox = renderCommand.boundingBox; + } + + // 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 offscreen = currentElementBoundingBox.x > (float)screenWidth || currentElementBoundingBox.y > (float)screenHeight || currentElementBoundingBox.x + currentElementBoundingBox.width < 0 || currentElementBoundingBox.y + currentElementBoundingBox.height < 0; + bool shouldRender = !offscreen; + switch (renderCommand.commandType) { + case CLAY_RENDER_COMMAND_TYPE_NONE: { + shouldRender = false; + break; + } + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + renderCommand.text = currentElement->text; + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { // We render borders on close because they need to render above children + shouldRender = false; + break; + } + default: break; + } + if (shouldRender) { + Clay_RenderCommandArray_Add(&Clay__renderCommands, 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) + } + + // Handle child alignment along the layout axis + if (currentElementTreeNode->layoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_TEXT) { + dfsBuffer.length += currentElement->children.length; + + Clay_Dimensions contentSize = (Clay_Dimensions) {0,0}; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + for (int i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = currentElement->children.elements[i]; + contentSize.width += childElement->dimensions.width; + contentSize.height = CLAY__MAX(contentSize.height, childElement->dimensions.height); + } + contentSize.width += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap); + float extraSpace = currentElement->dimensions.width - (float)layoutConfig->padding.x * 2 - 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; + } else { + for (int i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = currentElement->children.elements[i]; + contentSize.width = CLAY__MAX(contentSize.width, childElement->dimensions.width); + contentSize.height += childElement->dimensions.height; + } + contentSize.height += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap); + float extraSpace = currentElement->dimensions.height - (float)layoutConfig->padding.y * 2 - contentSize.height; + switch (layoutConfig->childAlignment.y) { + case CLAY_ALIGN_Y_TOP: extraSpace = 0; break; + case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break; + default: break; + } + currentElementTreeNode->nextChildOffset.y += extraSpace; + } + + if (scrollContainerData) { + scrollContainerData->contentSize = contentSize; + } + } + } else { + // DFS is returning upwards backwards + if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER) { + Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) { + .id = Clay__RehashWithNumber(currentElement->id, 11), + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, + }); + // Borders between elements are expressed as additional rectangle render commands + } else if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_BORDER_CONTAINER) { + Clay_Rectangle currentElementBoundingBox = (Clay_Rectangle) { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height }; + Clay_BorderContainerElementConfig *borderConfig = currentElement->elementConfig.borderElementConfig; + + Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) { + .id = currentElement->id, + .commandType = CLAY_RENDER_COMMAND_TYPE_BORDER, + .boundingBox = currentElementBoundingBox, + .config = currentElement->elementConfig + }); + + // Render border elements between children + if (borderConfig->betweenChildren.width > 0 && borderConfig->betweenChildren.color.a > 0) { + Clay_Vector2 borderOffset = { (float)layoutConfig->padding.x, (float)layoutConfig->padding.y }; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + for (int i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = currentElement->children.elements[i]; + if (i > 0) { + Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) { + .id = Clay__RehashWithNumber(currentElement->id, 5 + i), + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + .boundingBox = { currentElementBoundingBox.x + borderOffset.x, currentElementBoundingBox.y, (float)borderConfig->betweenChildren.width, currentElement->dimensions.height }, + .config = CLAY_RECTANGLE_CONFIG(.color = borderConfig->betweenChildren.color) + }); + } + borderOffset.x += (childElement->dimensions.width + (float)layoutConfig->childGap / 2); + } + } else { + for (int i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = currentElement->children.elements[i]; + if (i > 0) { + Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) { + .id = Clay__RehashWithNumber(currentElement->id, 5 + i), + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + .boundingBox = { currentElementBoundingBox.x, currentElementBoundingBox.y + borderOffset.y, currentElement->dimensions.width, (float)borderConfig->betweenChildren.width }, + .config = CLAY_RECTANGLE_CONFIG(.color = borderConfig->betweenChildren.color) + }); + } + borderOffset.y += (childElement->dimensions.height + (float)layoutConfig->childGap / 2); + } + } + } + } + dfsBuffer.length--; + continue; + } + + // Add children to the DFS buffer + if (currentElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_TEXT) { + for (int i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = currentElement->children.elements[i]; + // Alignment along non layout axis + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + currentElementTreeNode->nextChildOffset.y = currentElement->layoutConfig->padding.y; + float whiteSpaceAroundChild = currentElement->dimensions.height - (float)currentElement->layoutConfig->padding.y * 2 - 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.x; + float whiteSpaceAroundChild = currentElement->dimensions.width - (float)currentElement->layoutConfig->padding.x * 2 - 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 = (Clay_Vector2) { + 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__LayoutElementTreeNode) { + .layoutElement = childElement, + .position = (Clay_Vector2) { childPosition.x, childPosition.y }, + .nextChildOffset = (Clay_Vector2) { .x = (float)childElement->layoutConfig->padding.x, .y = (float)childElement->layoutConfig->padding.y }, + }; + Clay__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_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) { .id = Clay__RehashWithNumber(root->layoutElement->id, 11), .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END }); + } + } +} + +// PUBLIC API FROM HERE --------------------------------------- + +CLAY_WASM_EXPORT("Clay_MinMemorySize") +uint32_t Clay_MinMemorySize() { + Clay_Arena fakeArena = (Clay_Arena) { .capacity = INT64_MAX }; + Clay__InitializePersistentMemory(&fakeArena); + Clay__InitializeEphemeralMemory(&fakeArena); + return fakeArena.nextAllocation; +} + +CLAY_WASM_EXPORT("Clay_CreateArenaWithCapacityAndMemory") +Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *offset) { + Clay_Arena arena = (Clay_Arena) { + .capacity = capacity, + .memory = (char *)offset + }; + return arena; +} + +#ifndef CLAY_WASM +void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_String *text, Clay_TextElementConfig *config)) { + Clay__MeasureText = measureTextFunction; +} +#endif + +CLAY_WASM_EXPORT("Clay_SetPointerPosition") +void Clay_SetPointerPosition(Clay_Vector2 position) { + Clay__pointerPosition = position; + Clay__pointerOverIds.length = 0; + Clay__LayoutElementPointerArray dfsBuffer = Clay__layoutElementChildrenBuffer; + for (int rootIndex = 0; rootIndex < Clay__layoutElementTreeRoots.length; ++rootIndex) { + dfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex); + Clay__LayoutElementPointerArray_Add(&dfsBuffer, root->layoutElement); + Clay__treeNodeVisited.internalArray[0] = false; + while (dfsBuffer.length > 0) { + if (Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + dfsBuffer.length--; + continue; + } + Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + Clay_LayoutElement *currentElement = *Clay__LayoutElementPointerArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); + Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO I wish there was a way around this, maybe the fact that it's essentially a binary tree limits the cost, have to measure + if ((mapItem && Clay__PointIsInsideRect(position, mapItem->boundingBox)) || (!mapItem && Clay__PointIsInsideRect(position, (Clay_Rectangle) {0,0, currentElement->dimensions.width, currentElement->dimensions.height}))) { + Clay__int32_tArray_Add(&Clay__pointerOverIds, (int)currentElement->id); + if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_TEXT) { + dfsBuffer.length--; + continue; + } + for (int i = currentElement->children.length - 1; i >= 0; --i) { + Clay__LayoutElementPointerArray_Add(&dfsBuffer, currentElement->children.elements[i]); + Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked + } + } else { + dfsBuffer.length--; + } + } + } +} + +CLAY_WASM_EXPORT("Clay_Initialize") +void Clay_Initialize(Clay_Arena arena) { + Clay__internalArena = arena; + Clay__InitializePersistentMemory(&Clay__internalArena); + Clay__InitializeEphemeralMemory(&Clay__internalArena); + for (int i = 0; i < Clay__layoutElementsHashMap.capacity; ++i) { + Clay__layoutElementsHashMap.internalArray[i] = -1; + } + Clay__measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element" +} + +CLAY_WASM_EXPORT("Clay_UpdateScrollContainers") +void Clay_UpdateScrollContainers(bool isPointerActive, Clay_Vector2 scrollDelta, float deltaTime) { + // Don't apply scroll events to ancestors of the inner element + int32_t highestPriorityElementIndex = -1; + Clay__ScrollContainerData *highestPriorityScrollData = CLAY__NULL; + for (int i = 0; i < Clay__scrollContainerOffsets.length; i++) { + Clay__ScrollContainerData *scrollData = Clay__ScrollContainerDataArray_Get(&Clay__scrollContainerOffsets, i); + if (!scrollData->openThisFrame) { + Clay__ScrollContainerDataArray_RemoveSwapback(&Clay__scrollContainerOffsets, 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__ScrollContainerDataArray_RemoveSwapback(&Clay__scrollContainerOffsets, 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_Vector2){0,0}; + scrollData->scrollOrigin = (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__MAX(CLAY__MIN(scrollData->scrollPosition.x, 0), -(scrollData->contentSize.width - scrollData->layoutElement->dimensions.width)); + + 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__MAX(CLAY__MIN(scrollData->scrollPosition.y, 0), -(scrollData->contentSize.height - scrollData->layoutElement->dimensions.height)); + + for (int j = 0; j < Clay__pointerOverIds.length; ++j) { // TODO n & m are small here but this being n*m gives me the creeps + if (scrollData->layoutElement->id == *Clay__int32_tArray_Get(&Clay__pointerOverIds, j)) { + highestPriorityElementIndex = j; + highestPriorityScrollData = scrollData; + } + } + } + + if (highestPriorityElementIndex > -1 && highestPriorityScrollData) { + Clay_LayoutElement *scrollElement = highestPriorityScrollData->layoutElement; + bool canScrollVertically = scrollElement->elementConfig.scrollElementConfig->vertical && highestPriorityScrollData->contentSize.height > scrollElement->dimensions.height; + bool canScrollHorizontally = scrollElement->elementConfig.scrollElementConfig->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_Vector2){0}; + if (!highestPriorityScrollData->pointerScrollActive) { + highestPriorityScrollData->pointerOrigin = Clay__pointerPosition; + 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 + (Clay__pointerPosition.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 + (Clay__pointerPosition.y - highestPriorityScrollData->pointerOrigin.y); + highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - highestPriorityScrollData->boundingBox.height)); + scrollDeltaX = highestPriorityScrollData->scrollPosition.x - oldYScrollPosition; + } + if (scrollDeltaX > -0.1f && scrollDeltaX < 0.1f && scrollDeltaY > -0.1f && scrollDeltaY < 0.1f && highestPriorityScrollData->momentumTime > 0.5f) { + highestPriorityScrollData->momentumTime = 0; + highestPriorityScrollData->pointerOrigin = Clay__pointerPosition; + 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(int screenWidth, int screenHeight) { + Clay__InitializeEphemeralMemory(&Clay__internalArena); + // Set up the root container that covers the entire window + Clay_LayoutElement rootLayoutElement = (Clay_LayoutElement){.layoutConfig = CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED((float)screenWidth), CLAY_SIZING_FIXED((float)screenHeight)})}; + Clay__openLayoutElement = Clay_LayoutElementArray_Add(&Clay__layoutElements, rootLayoutElement); + Clay__LayoutElementPointerArray_Add(&Clay__openLayoutElementStack, Clay__openLayoutElement); + Clay__LayoutElementTreeRootArray_Add(&Clay__layoutElementTreeRoots, (Clay__LayoutElementTreeRoot) { .layoutElement = Clay__openLayoutElement }); +} + +CLAY_WASM_EXPORT("Clay_EndLayout") +Clay_RenderCommandArray Clay_EndLayout(int screenWidth, int screenHeight) +{ + Clay__AttachContainerChildren(); + Clay__CalculateFinalLayout(screenWidth, screenHeight); + return Clay__renderCommands; +} + +CLAY_WASM_EXPORT("Clay_PointerOver") +bool Clay_PointerOver(uint32_t id) { // TODO return priority for separating multiple results + for (int i = 0; i < Clay__pointerOverIds.length; ++i) { + if (*Clay__int32_tArray_Get(&Clay__pointerOverIds, i) == id) { + return true; + } + } + return false; +} + +#endif //CLAY_IMPLEMENTATION diff --git a/examples/clay-official-website/CMakeLists.txt b/examples/clay-official-website/CMakeLists.txt new file mode 100644 index 0000000..be7aa21 --- /dev/null +++ b/examples/clay-official-website/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.28) +project(clay_official_website C) + +set(CMAKE_C_STANDARD 99) + +add_executable(clay_official_website main.c) + +target_compile_options(clay_official_website PUBLIC -DCLAY_OVERFLOW_TRAP -Wno-initializer-overrides) +target_include_directories(clay_official_website PUBLIC .) + +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + +add_custom_command( + TARGET clay_official_website POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/resources + ${CMAKE_CURRENT_BINARY_DIR}/resources) \ No newline at end of file diff --git a/examples/clay-official-website/build.sh b/examples/clay-official-website/build.sh new file mode 100755 index 0000000..e10fc68 --- /dev/null +++ b/examples/clay-official-website/build.sh @@ -0,0 +1,17 @@ +mkdir -p build/clay \ +&& clang \ +-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/clay/index.html && cp -r fonts/ build/clay/fonts \ +&& cp index.html build/clay/index.html && cp -r images/ build/clay/images \ No newline at end of file diff --git a/examples/clay-official-website/fonts/Calistoga-Regular.ttf b/examples/clay-official-website/fonts/Calistoga-Regular.ttf new file mode 100644 index 0000000..3fc1c1e Binary files /dev/null and b/examples/clay-official-website/fonts/Calistoga-Regular.ttf differ diff --git a/examples/clay-official-website/fonts/Quicksand-Semibold.ttf b/examples/clay-official-website/fonts/Quicksand-Semibold.ttf new file mode 100644 index 0000000..27106d0 Binary files /dev/null and b/examples/clay-official-website/fonts/Quicksand-Semibold.ttf differ diff --git a/examples/clay-official-website/images/check_1.png b/examples/clay-official-website/images/check_1.png new file mode 100644 index 0000000..280fd23 Binary files /dev/null and b/examples/clay-official-website/images/check_1.png differ diff --git a/examples/clay-official-website/images/check_2.png b/examples/clay-official-website/images/check_2.png new file mode 100644 index 0000000..7972581 Binary files /dev/null and b/examples/clay-official-website/images/check_2.png differ diff --git a/examples/clay-official-website/images/check_3.png b/examples/clay-official-website/images/check_3.png new file mode 100644 index 0000000..fb60187 Binary files /dev/null and b/examples/clay-official-website/images/check_3.png differ diff --git a/examples/clay-official-website/images/check_4.png b/examples/clay-official-website/images/check_4.png new file mode 100644 index 0000000..a938f81 Binary files /dev/null and b/examples/clay-official-website/images/check_4.png differ diff --git a/examples/clay-official-website/images/check_5.png b/examples/clay-official-website/images/check_5.png new file mode 100644 index 0000000..ea6cfca Binary files /dev/null and b/examples/clay-official-website/images/check_5.png differ diff --git a/examples/clay-official-website/images/declarative.png b/examples/clay-official-website/images/declarative.png new file mode 100644 index 0000000..5cd4abb Binary files /dev/null and b/examples/clay-official-website/images/declarative.png differ diff --git a/examples/clay-official-website/images/renderer.png b/examples/clay-official-website/images/renderer.png new file mode 100644 index 0000000..92537c3 Binary files /dev/null and b/examples/clay-official-website/images/renderer.png differ diff --git a/examples/clay-official-website/index.html b/examples/clay-official-website/index.html new file mode 100644 index 0000000..7e75f17 --- /dev/null +++ b/examples/clay-official-website/index.html @@ -0,0 +1,688 @@ + + + + + + + Clay - UI Layout Library + + + + + + \ No newline at end of file diff --git a/examples/clay-official-website/main.c b/examples/clay-official-website/main.c new file mode 100644 index 0000000..ed959a3 --- /dev/null +++ b/examples/clay-official-website/main.c @@ -0,0 +1,349 @@ +#define CLAY_EXTEND_CONFIG_RECTANGLE Clay_String link; bool cursorPointer; +#define CLAY_EXTEND_CONFIG_IMAGE Clay_String sourceURL; +#define CLAY_EXTEND_CONFIG_TEXT bool disablePointerEvents; +#include "../../clay.h" + +double windowWidth = 1024, windowHeight = 768; +float modelPageOneZRotation = 0; +int ACTIVE_RENDERER_INDEX = 0; + +const uint32_t FONT_ID_TITLE_56 = 0; +const uint32_t FONT_ID_BODY_24 = 1; +const uint32_t FONT_ID_BODY_16 = 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}; +Clay_Color COLOR_LIGHT_HOVER = (Clay_Color) {224, 215, 210, 255}; +Clay_Color COLOR_BUTTON_HOVER = (Clay_Color) {238, 227, 225, 255}; +Clay_Color COLOR_BROWN = (Clay_Color) {61, 26, 5, 255}; +//Clay_Color COLOR_RED = (Clay_Color) {252, 67, 27, 255}; +Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; +Clay_Color COLOR_RED_HOVER = (Clay_Color) {148, 46, 8, 255}; +Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; +Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255}; +Clay_Color COLOR_TEAL = (Clay_Color) {111, 173, 162, 255}; +Clay_Color COLOR_BLUE_DARK = (Clay_Color) {2, 32, 82, 255}; + +// Colors for top stripe +Clay_Color COLOR_TOP_BORDER_1 = (Clay_Color) {168, 66, 28, 255}; +Clay_Color COLOR_TOP_BORDER_2 = (Clay_Color) {223, 110, 44, 255}; +Clay_Color COLOR_TOP_BORDER_3 = (Clay_Color) {225, 138, 50, 255}; +Clay_Color COLOR_TOP_BORDER_4 = (Clay_Color) {236, 189, 80, 255}; +Clay_Color COLOR_TOP_BORDER_5 = (Clay_Color) {240, 213, 137, 255}; + +Clay_Color COLOR_BLOB_BORDER_1 = (Clay_Color) {168, 66, 28, 255}; +Clay_Color COLOR_BLOB_BORDER_2 = (Clay_Color) {203, 100, 44, 255}; +Clay_Color COLOR_BLOB_BORDER_3 = (Clay_Color) {225, 138, 50, 255}; +Clay_Color COLOR_BLOB_BORDER_4 = (Clay_Color) {236, 159, 70, 255}; +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 = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }; +Clay_TextElementConfig blobTextConfig = (Clay_TextElementConfig) { .fontId = FONT_ID_BODY_24, .fontSize = 30, .textColor = COLOR_LIGHT }; + +void LandingPageBlob(int index, int fontSize, Clay_Color color, Clay_String text, Clay_String imageURL) { + CLAY_BORDER_CONTAINER(CLAY_IDI("HeroBlob", index), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = {16, 16}, .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, color, 10), { + CLAY_IMAGE(CLAY_IDI("CheckImage", index), CLAY_LAYOUT(.sizing = { CLAY_SIZING_FIXED(32) }), CLAY_IMAGE_CONFIG(.sourceDimensions = { 128, 128 }, .sourceURL = imageURL), {}); + CLAY_TEXT(CLAY_IDI("HeroBlobText", index), text, CLAY_TEXT_CONFIG(.fontSize = fontSize, .fontId = FONT_ID_BODY_24, .textColor = color)); + }); +} + +void LandingPageDesktop() { + CLAY_CONTAINER(CLAY_ID("LandingPage1Desktop"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { .x = 50 }), { + CLAY_BORDER_CONTAINER(CLAY_ID("LandingPage1"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), { + CLAY_CONTAINER(CLAY_ID("LeftText"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_PERCENT(0.55f) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { + CLAY_TEXT(CLAY_ID("LeftTextTitle"), CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG(.fontSize = 56, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); + CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(32) }), {}); + CLAY_TEXT(CLAY_ID("LeftTextTagline"), CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE)); + }); + CLAY_CONTAINER(CLAY_ID("HeroImageOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_PERCENT(0.45f) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16), { + 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_CONTAINER(CLAY_ID("LandingPage1Mobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 32 }, .childGap = 32), { + CLAY_CONTAINER(CLAY_ID("LeftText"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { + CLAY_TEXT(CLAY_ID("LeftTextTitle"), CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); + CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(32) }), {}); + CLAY_TEXT(CLAY_ID("LeftTextTagline"), CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG(.fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE)); + }); + CLAY_CONTAINER(CLAY_ID("HeroImageOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW() }, .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_CONTAINER(CLAY_ID("FeatureBlocksOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }), { + CLAY_BORDER_CONTAINER(CLAY_ID("FeatureBlocksInner"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } ), CLAY_BORDER_CONFIG(.betweenChildren = { .width = 2, .color = COLOR_RED }), { + Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED ); + CLAY_CONTAINER(CLAY_ID("HFileBoxOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 32}, .childGap = 8), { + CLAY_RECTANGLE(CLAY_ID("HFileIncludeOuter"), CLAY_LAYOUT(.padding = {8, 4}), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8)), { + CLAY_TEXT(CLAY_IDI("HFileBoxText", 2), CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT)); + }); + CLAY_TEXT(CLAY_ID("HFileSecondLine"), CLAY_STRING("~2000 lines of C99."), textConfig); + CLAY_TEXT(CLAY_IDI("HFileBoxText", 5), CLAY_STRING("Zero dependencies, including no C standard library."), textConfig); + }); + CLAY_CONTAINER(CLAY_ID("BringYourOwnRendererOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50, .y = 32}, .childGap = 8), { + CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 1), CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG(.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE)); + CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 2), CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig); + CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 3), CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig); + }); + }); + }); +} + +void FeatureBlocksMobile() { + CLAY_BORDER_CONTAINER(CLAY_ID("FeatureBlocksInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, ), CLAY_BORDER_CONFIG(.betweenChildren = { .width = 2, .color = COLOR_RED }), { + Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED ); + CLAY_CONTAINER(CLAY_ID("HFileBoxOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 32}, .childGap = 8), { + CLAY_RECTANGLE(CLAY_ID("HFileIncludeOuter"), CLAY_LAYOUT(.padding = {8, 4}), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8)), { + CLAY_TEXT(CLAY_IDI("HFileBoxText", 2), CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT)); + }); + CLAY_TEXT(CLAY_ID("HFileSecondLine"), CLAY_STRING("~2000 lines of C99."), textConfig); + CLAY_TEXT(CLAY_IDI("HFileBoxText", 5), CLAY_STRING("Zero dependencies, including no C standard library."), textConfig); + }); + CLAY_CONTAINER(CLAY_ID("BringYourOwnRendererOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, .y = 32}, .childGap = 8), { + CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 1), CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG(.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE)); + CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 2), CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig); + CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 3), CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig); + }); + }); +} + +void DeclarativeSyntaxPageDesktop() { + CLAY_CONTAINER(CLAY_ID("SyntaxPageDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50}), { + CLAY_BORDER_CONTAINER(CLAY_ID("SyntaxPage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), { + CLAY_CONTAINER(CLAY_ID("SyntaxPageLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { + CLAY_TEXT(CLAY_ID("SyntaxPageTextTitle"), CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); + CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); + CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle1"), CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle2"), CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle3"), CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + }); + CLAY_CONTAINER(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER}), { + CLAY_IMAGE(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 568) }), CLAY_IMAGE_CONFIG(.sourceDimensions = {1136, 1194}, .sourceURL = CLAY_STRING("/clay/images/declarative.png")), {}); + }); + }); + }); +} + +void DeclarativeSyntaxPageMobile() { + CLAY_CONTAINER(CLAY_ID("SyntaxPageDesktop"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {16, 32}, .childGap = 16), { + CLAY_CONTAINER(CLAY_ID("SyntaxPageLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { + CLAY_TEXT(CLAY_ID("SyntaxPageTextTitle"), CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); + CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); + CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle1"), CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle2"), CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle3"), CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + }); + CLAY_CONTAINER(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER}), { + CLAY_IMAGE(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 568) }), CLAY_IMAGE_CONFIG(.sourceDimensions = {1136, 1194}, .sourceURL = 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_RECTANGLE(CLAY_ID("PerformanceDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 82, 32}, .childGap = 64), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED), { + CLAY_CONTAINER(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { + CLAY_TEXT(CLAY_ID("PerformanceTextTitle"), CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); + CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); + CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 1), CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); + CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 2), CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); + CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 3), CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); + }); + CLAY_CONTAINER(CLAY_ID("PerformanceRightImageOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER}), { + CLAY_BORDER_CONTAINER(CLAY_ID(""), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(400) }), CLAY_BORDER_CONFIG_ALL(.width = 2, .color = COLOR_LIGHT), { + CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.3f + 0.4f * lerpValue), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {32, 32}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)), { + CLAY_TEXT(CLAY_ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); + }); + CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {32, 32}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)), { + CLAY_TEXT(CLAY_ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); + }); + }); + }); + }); +} + +void HighPerformancePageMobile(float lerpValue) { + CLAY_RECTANGLE(CLAY_ID("PerformanceMobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED), { + CLAY_CONTAINER(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { + CLAY_TEXT(CLAY_ID("PerformanceTextTitle"), CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); + CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); + CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 1), CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); + CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 2), CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); + CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 3), CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); + }); + CLAY_CONTAINER(CLAY_ID("PerformanceRightImageOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = {CLAY_ALIGN_X_CENTER}), { + CLAY_BORDER_CONTAINER(CLAY_ID(""), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(400) }), CLAY_BORDER_CONFIG_ALL(.width = 2, .color = COLOR_LIGHT), { + CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.35f + 0.3f * lerpValue), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)), { + CLAY_TEXT(CLAY_ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); + }); + CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)), { + CLAY_TEXT(CLAY_ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); + }); + }); + }); + }); +} + +void RendererButtonActive(uint32_t id, int index, Clay_String text) { + CLAY_RECTANGLE(id, CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = Clay_PointerOver(id) ? COLOR_RED_HOVER : COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(10)), { + CLAY_TEXT(CLAY_ID("RendererButtonActiveText"), text, CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); + }); +} + +void RendererButtonInactive(uint32_t id, int index, Clay_String text) { + CLAY_BORDER_CONTAINER(id, CLAY_LAYOUT(), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, COLOR_RED, 10), { + CLAY_RECTANGLE(CLAY_IDI("RendererButtonInactiveInner", index), CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = Clay_PointerOver(id) ? COLOR_LIGHT_HOVER : COLOR_LIGHT, .cornerRadius = CLAY_CORNER_RADIUS(10), .cursorPointer = true), { + CLAY_TEXT(CLAY_IDI("RendererButtonInactiveText", index), text, CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + }); + }); +} + +void RendererPageDesktop() { + CLAY_CONTAINER(CLAY_ID("RendererPageDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50}), { + CLAY_BORDER_CONTAINER(CLAY_ID("RendererPage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), { + CLAY_CONTAINER(CLAY_ID("RendererLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { + CLAY_TEXT(CLAY_ID("RendererTextTitle"), CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); + CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); + CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 1), CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 2), CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 3), CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + }); + CLAY_CONTAINER(CLAY_ID("RendererRightText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .childAlignment = {CLAY_ALIGN_X_CENTER}, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16), { + CLAY_TEXT(CLAY_ID("RendererTextRightTitle"), CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE)); + CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 32) }), {}); + if (ACTIVE_RENDERER_INDEX == 0) { + RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 0, CLAY_STRING("HTML Renderer")); + RendererButtonInactive(CLAY_ID("RendererSelectButtonCanvas"), 1, CLAY_STRING("Canvas Renderer")); + } else { + RendererButtonInactive(CLAY_ID("RendererSelectButtonHTML"), 0, CLAY_STRING("HTML Renderer")); + RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 1, CLAY_STRING("Canvas Renderer")); + } + }); + }); + }); +} + +void RendererPageMobile() { + CLAY_RECTANGLE(CLAY_ID("RendererMobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { + CLAY_CONTAINER(CLAY_ID("RendererLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { + CLAY_TEXT(CLAY_ID("RendererTextTitle"), CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); + CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); + CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 1), CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 2), CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 3), CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); + }); + CLAY_CONTAINER(CLAY_ID("RendererRightText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16), { + CLAY_TEXT(CLAY_ID("RendererTextRightTitle"), CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE)); + CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 32) }), {}); + if (ACTIVE_RENDERER_INDEX == 0) { + RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 0, CLAY_STRING("HTML Renderer")); + RendererButtonInactive(CLAY_ID("RendererSelectButtonCanvas"), 1, CLAY_STRING("Canvas Renderer")); + } else { + RendererButtonInactive(CLAY_ID("RendererSelectButtonHTML"), 0, CLAY_STRING("HTML Renderer")); + RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 1, CLAY_STRING("Canvas Renderer")); + } + }); + }); +} + +Clay_RenderCommandArray CreateLayout(float lerpValue) { + bool mobileScreen = windowWidth < 750; + Clay_BeginLayout((int)windowWidth, (int)windowHeight); + CLAY_RECTANGLE(CLAY_ID("OuterContainer"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { + CLAY_CONTAINER(CLAY_ID("Header"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(50) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .childGap = 24, .padding = { 32 }), { + CLAY_TEXT(CLAY_ID("Logo"), CLAY_STRING("Clay"), &headerTextConfig); + CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }), {}); + + if (!mobileScreen) { + CLAY_TEXT(CLAY_ID("LinkFeatures"), CLAY_STRING("Features"), &headerTextConfig); + CLAY_TEXT(CLAY_ID("LinkDocs"), CLAY_STRING("Docs"), &headerTextConfig); + } + uint32_t githubButtonId = CLAY_ID("HeaderButtonGithub"); + CLAY_BORDER_CONTAINER(CLAY_ID("LinkGithubOuter"), CLAY_LAYOUT(), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, COLOR_RED, 10), { + CLAY_RECTANGLE(githubButtonId, CLAY_LAYOUT(.padding = {16, 6}), CLAY_RECTANGLE_CONFIG(.cornerRadius = CLAY_CORNER_RADIUS(10), .link = CLAY_STRING("https://github.com/nicbarker/clay"), .color = Clay_PointerOver(githubButtonId) ? COLOR_LIGHT_HOVER : COLOR_LIGHT), { + CLAY_TEXT(CLAY_ID("LinkGithubText"), CLAY_STRING("Github"), CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255})); + }); + }); + }); + CLAY_RECTANGLE(CLAY_ID("TopBorder1"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_5), {}); + CLAY_RECTANGLE(CLAY_ID("TopBorder2"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_4), {}); + CLAY_RECTANGLE(CLAY_ID("TopBorder3"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_3), {}); + CLAY_RECTANGLE(CLAY_ID("TopBorder4"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_2), {}); + CLAY_RECTANGLE(CLAY_ID("TopBorder5"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_1), {}); + CLAY_RECTANGLE(CLAY_ID("ScrollContainerBackgroundRectangle"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { + CLAY_SCROLL_CONTAINER(CLAY_ID("OuterScrollContainer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_SCROLL_CONFIG(.vertical = true), { + CLAY_BORDER_CONTAINER(CLAY_ID("ScrollContainerInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }), CLAY_BORDER_CONFIG(.betweenChildren = {2, COLOR_RED}), { + if (mobileScreen) { + LandingPageMobile(); + FeatureBlocksMobile(); + DeclarativeSyntaxPageMobile(); + HighPerformancePageMobile(lerpValue); + RendererPageMobile(); + } else { + LandingPageDesktop(); + FeatureBlocksDesktop(); + DeclarativeSyntaxPageDesktop(); + HighPerformancePageDesktop(lerpValue); + RendererPageDesktop(); + } + }); + }); + }); + }); + return Clay_EndLayout((int)windowWidth, (int)windowHeight); +} + +float animationLerpValue = -1.0f; + +CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(float width, float height, float mouseWheelX, float mouseWheelY, float mousePositionX, float mousePositionY, bool isTouchDown, bool isMouseDown, float deltaTime) { + windowWidth = width; + windowHeight = height; + if (deltaTime == deltaTime) { // NaN propagation can cause pain here + animationLerpValue += deltaTime; + if (animationLerpValue > 1) { + animationLerpValue -= 2; + } + } + + if (isTouchDown || isMouseDown) { + if (Clay_PointerOver(CLAY_ID("RendererSelectButtonHTML"))) { + ACTIVE_RENDERER_INDEX = 0; + } else if (Clay_PointerOver(CLAY_ID("RendererSelectButtonCanvas"))) { + ACTIVE_RENDERER_INDEX = 1; + } + } + //---------------------------------------------------------------------------------- + // Handle scroll containers + Clay_SetPointerPosition((Clay_Vector2) {mousePositionX, mousePositionY}); + Clay_UpdateScrollContainers(isTouchDown, (Clay_Vector2) {mouseWheelX, mouseWheelY}, deltaTime); + return CreateLayout(animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue)); + //---------------------------------------------------------------------------------- +} \ No newline at end of file diff --git a/examples/raylib-sidebar-scrolling-container/CMakeLists.txt b/examples/raylib-sidebar-scrolling-container/CMakeLists.txt new file mode 100644 index 0000000..1a6e2ee --- /dev/null +++ b/examples/raylib-sidebar-scrolling-container/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.28) +project(clay_examples_raylib_sidebar_scrolling_container C) + +# 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 "master" + GIT_PROGRESS TRUE +) + +FetchContent_MakeAvailable(raylib) + +set(CMAKE_C_STANDARD 99) + +add_executable(clay_examples_raylib_sidebar_scrolling_container main.c) + +target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC -DCLAY_OVERFLOW_TRAP -Wno-initializer-overrides) +target_include_directories(clay_examples_raylib_sidebar_scrolling_container PUBLIC .) + +target_link_libraries(clay_examples_raylib_sidebar_scrolling_container PUBLIC raylib) +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + +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) \ No newline at end of file diff --git a/examples/raylib-sidebar-scrolling-container/main.c b/examples/raylib-sidebar-scrolling-container/main.c new file mode 100644 index 0000000..9ac1349 --- /dev/null +++ b/examples/raylib-sidebar-scrolling-container/main.c @@ -0,0 +1,168 @@ +#include "../../clay.h" +#include "../../renderers/raylib/clay_renderer_raylib.c" + +double windowWidth = 1024, windowHeight = 768; + +const uint32_t FONT_ID_BODY_24 = 0; +const uint32_t FONT_ID_BODY_16 = 1; +Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; +Clay_Color 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("Profile Page one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen"); +Clay_TextElementConfig headerTextConfig = (Clay_TextElementConfig) { .fontId = FONT_ID_BODY_16, .fontSize = 16, .textColor = {0,0,0,255} }; + +// Examples of re-usable "Components" +void RenderHeaderButton(uint16_t index, Clay_String text) { + uint32_t buttonId = CLAY_IDI("HeaderButton", index); + CLAY_RECTANGLE(buttonId, CLAY_LAYOUT(.padding = {16, 8}), CLAY_RECTANGLE_CONFIG(.color = Clay_PointerOver(buttonId) ? COLOR_BLUE : COLOR_ORANGE), { + CLAY_TEXT(CLAY_IDI("Button", index), text, &headerTextConfig); + }); +} + +Clay_LayoutConfig dropdownTextItemLayout = (Clay_LayoutConfig) { .padding = {8, 4} }; +Clay_RectangleElementConfig dropdownRectangleConfig = (Clay_RectangleElementConfig) { .color = {180, 180, 180, 255} }; +Clay_TextElementConfig dropdownTextElementConfig = (Clay_TextElementConfig) { .fontSize = 24, .textColor = {255,255,255,255} }; + +void RenderDropdownTextItem() { + CLAY_RECTANGLE(CLAY_ID("ScrollContainerItem"), &dropdownTextItemLayout, &dropdownRectangleConfig, { // We can save a lot of memory by re-using configs in loops rather than redefining them + CLAY_TEXT(CLAY_ID("ScrollContainerText"), CLAY_STRING("I'm a text field in a scroll container."), &dropdownTextElementConfig); + }); +} + +Clay_RenderCommandArray CreateLayout() { + Clay_BeginLayout((int)windowWidth, (int)windowHeight); + CLAY_RECTANGLE(CLAY_ID("OuterContainer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .padding = { 16, 16 }, .childGap = 16), CLAY_RECTANGLE_CONFIG(.color = {200, 200, 200, 255}), { + CLAY_RECTANGLE(CLAY_ID("SideBar"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16}, .childGap = 16), CLAY_RECTANGLE_CONFIG(.color = {150, 150, 255, 255}), { + CLAY_RECTANGLE(CLAY_ID("ProfilePictureOuter"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }, .padding = { 8, 8 }, .childGap = 8, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }), CLAY_RECTANGLE_CONFIG(.color = {130, 130, 255, 255}), { + CLAY_IMAGE(CLAY_ID("ProfilePicture"), CLAY_LAYOUT( .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }), CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .sourceDimensions = {60, 60}), {}); + CLAY_TEXT(CLAY_ID("ProfileTitle"), profileText, CLAY_TEXT_CONFIG(.fontSize = 24, .textColor = {0, 0, 0, 255})); + }); + CLAY_RECTANGLE(CLAY_ID("SidebarBlob1"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) }), CLAY_RECTANGLE_CONFIG(.color = {110, 110, 255, 255}), {}); + CLAY_RECTANGLE(CLAY_ID("SidebarBlob2"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) }), CLAY_RECTANGLE_CONFIG(.color = {110, 110, 255, 255}), {}); + CLAY_RECTANGLE(CLAY_ID("SidebarBlob3"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) }), CLAY_RECTANGLE_CONFIG(.color = {110, 110, 255, 255}), {}); + CLAY_RECTANGLE(CLAY_ID("SidebarBlob4"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) }), CLAY_RECTANGLE_CONFIG(.color = {110, 110, 255, 255}), {}); + }); +// + CLAY_CONTAINER(CLAY_ID("RightPanel"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW() }, .childGap = 16), { + CLAY_RECTANGLE(CLAY_ID("HeaderBar"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }, .childAlignment = { .x = CLAY_ALIGN_X_RIGHT }, .padding = {8, 8}, .childGap = 8), CLAY_RECTANGLE_CONFIG(.color = {180, 180, 180, 255}), { + RenderHeaderButton(1, CLAY_STRING("Header Item 1")); + RenderHeaderButton(2, CLAY_STRING("Header Item 2")); + RenderHeaderButton(3, CLAY_STRING("Header Item 3")); + }); + CLAY_SCROLL_CONTAINER(CLAY_ID("MainContent"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }), CLAY_SCROLL_CONFIG(.vertical = true), { + CLAY_RECTANGLE(CLAY_ID("MainContentInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {16, 16}, .childGap = 16), CLAY_RECTANGLE_CONFIG(.color = {200, 200, 255, 255}), { + CLAY_FLOATING_CONTAINER(CLAY_ID("FloatingContainer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_FLOATING_CONFIG(.zIndex = 1, .attachment = { CLAY_ATTACH_POINT_CENTER_TOP, CLAY_ATTACH_POINT_CENTER_TOP }, .offset = {0, -16}), { + CLAY_RECTANGLE(CLAY_ID("FloatingContainerBackground"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = {140,80, 200, 200}), { + CLAY_TEXT(CLAY_ID("FloatingContainerText"), CLAY_STRING("I'm an inline floating container."), CLAY_TEXT_CONFIG(.fontSize = 24, .textColor = {255,255,255,255})); + }); + }); + + CLAY_TEXT(CLAY_ID("BodyText1"), + 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_RECTANGLE(CLAY_ID("Photos"), CLAY_LAYOUT(.childGap = 16, .padding = { 16, 16 }), CLAY_RECTANGLE_CONFIG(.color = {180, 180, 220, 255}), { + CLAY_IMAGE(CLAY_ID("Picture1"), CLAY_LAYOUT( .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }), CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .sourceDimensions = {120, 120}), {}); + CLAY_IMAGE(CLAY_ID("Picture2"), CLAY_LAYOUT( .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }), CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .sourceDimensions = {120, 120}), {}); + CLAY_IMAGE(CLAY_ID("Picture3"), CLAY_LAYOUT( .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }), CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .sourceDimensions = {120, 120}), {}); + }); + + CLAY_TEXT(CLAY_ID("BodyText2"), + 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, .lineSpacing = 20, .textColor = {0,0,0,255})); + + CLAY_TEXT(CLAY_ID("BodyText3"), + 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_RECTANGLE(CLAY_ID("Photos"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, .childGap = 16, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = {180, 180, 220, 255}), { + CLAY_IMAGE(CLAY_ID("Picture2"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }), CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .sourceDimensions = {120, 120}), {}); + CLAY_RECTANGLE(CLAY_ID("Picture1"), CLAY_LAYOUT(.childAlignment = { .x = CLAY_ALIGN_X_CENTER }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {8, 8}), CLAY_RECTANGLE_CONFIG(.color = {170, 170, 220, 255}), { + CLAY_IMAGE(CLAY_ID("ProfilePicture"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }), CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .sourceDimensions = {60, 60}), {}); + CLAY_TEXT(CLAY_ID("ProfileTitle"), CLAY_STRING("Image caption below"), CLAY_TEXT_CONFIG(.fontSize = 24, .textColor = {0,0,0,255})); + }); + CLAY_IMAGE(CLAY_ID("Picture3"), CLAY_LAYOUT( .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }), CLAY_IMAGE_CONFIG(.imageData = &profilePicture, .sourceDimensions = {120, 120}), {}); + }); +// + CLAY_TEXT(CLAY_ID("BodyText4"), + 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_FLOATING_CONTAINER(CLAY_ID("Blob4Floating"), &CLAY_LAYOUT_DEFAULT, CLAY_FLOATING_CONFIG(.zIndex = 1, .parentId = CLAY_ID("SidebarBlob4")), { + CLAY_SCROLL_CONTAINER(CLAY_ID("ScrollContainer"), CLAY_LAYOUT(.sizing = { .height = CLAY_SIZING_FIXED(200) }, .childGap = 2), CLAY_SCROLL_CONFIG(.vertical = true), { + CLAY_FLOATING_CONTAINER(CLAY_ID("FloatingContainer"), CLAY_LAYOUT(), CLAY_FLOATING_CONFIG(.zIndex = 1), { + CLAY_RECTANGLE(CLAY_ID("FLoatingContainerInner"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = {140,80, 200, 200}), { + CLAY_TEXT(CLAY_ID("FloatingContainerText"), CLAY_STRING("I'm an inline floating container."), CLAY_TEXT_CONFIG(.fontSize = 24, .textColor = {255,255,255,255})); + }); + }); + CLAY_RECTANGLE(CLAY_ID("ScrollContainerInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM), CLAY_RECTANGLE_CONFIG(.color = {160, 160, 160, 255}), { + for (int i = 0; i < 100; i++) { + RenderDropdownTextItem(); + } + }); + }); + }); + }); + return Clay_EndLayout(GetScreenWidth(), GetScreenHeight()); +} + +int display_size_changed = 0; + +void UpdateDrawFrame(void) +{ + float mouseWheelX = 0, mouseWheelY = 0; + Vector2 mouseWheelDelta = GetMouseWheelMoveV(); + mouseWheelX = mouseWheelDelta.x; + mouseWheelY = mouseWheelDelta.y; + //---------------------------------------------------------------------------------- + // Handle scroll containers + Clay_SetPointerPosition(RAYLIB_VECTOR2_TO_CLAY_VECTOR2(GetMousePosition())); + Clay_UpdateScrollContainers(IsMouseButtonDown(0), (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(); + Clay_Raylib_Render(renderCommands); + EndDrawing(); +// printf("render time: %f ms\n", (GetTime() - currentTime) * 1000); + + //---------------------------------------------------------------------------------- +} + +int main(void) { + uint64_t totalMemorySize = Clay_MinMemorySize(); + Clay_Arena clayMemory = (Clay_Arena) { .label = CLAY_STRING("Clay Memory Arena"), .memory = malloc(totalMemorySize), .capacity = totalMemorySize }; + Clay_SetMeasureTextFunction(Raylib_MeasureText); + Clay_Initialize(clayMemory); + Clay_Raylib_Initialize(FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT); + profilePicture = LoadTextureFromImage(LoadImage("resources/profile-picture.png")); + Raylib_fonts[FONT_ID_BODY_24] = (Raylib_Font) { + .font = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400), + .fontId = FONT_ID_BODY_24, + }; + SetTextureFilter(Raylib_fonts[FONT_ID_BODY_24].font.texture, TEXTURE_FILTER_TRILINEAR); + + Raylib_fonts[FONT_ID_BODY_16] = (Raylib_Font) { + .font = LoadFontEx("resources/Roboto-Regular.ttf", 32, 0, 400), + .fontId = FONT_ID_BODY_16, + }; + SetTextureFilter(Raylib_fonts[FONT_ID_BODY_16].font.texture, TEXTURE_FILTER_TRILINEAR); + + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + UpdateDrawFrame(); + } + return 0; +} diff --git a/examples/raylib-sidebar-scrolling-container/resources/Roboto-Regular.ttf b/examples/raylib-sidebar-scrolling-container/resources/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/examples/raylib-sidebar-scrolling-container/resources/Roboto-Regular.ttf differ diff --git a/examples/raylib-sidebar-scrolling-container/resources/RobotoMono-Medium.ttf b/examples/raylib-sidebar-scrolling-container/resources/RobotoMono-Medium.ttf new file mode 100644 index 0000000..f6c149a Binary files /dev/null and b/examples/raylib-sidebar-scrolling-container/resources/RobotoMono-Medium.ttf differ diff --git a/examples/raylib-sidebar-scrolling-container/resources/profile-picture.png b/examples/raylib-sidebar-scrolling-container/resources/profile-picture.png new file mode 100644 index 0000000..8c4ea3e Binary files /dev/null and b/examples/raylib-sidebar-scrolling-container/resources/profile-picture.png differ diff --git a/generator/array_add.template.c b/generator/array_add.template.c new file mode 100644 index 0000000..847cbcc --- /dev/null +++ b/generator/array_add.template.c @@ -0,0 +1,7 @@ +$TYPE$ *$NAME$_Add($NAME$ *array, $TYPE$ item) { + if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return $DEFAULT_VALUE$; +} \ No newline at end of file diff --git a/generator/array_define.template.c b/generator/array_define.template.c new file mode 100644 index 0000000..2ef7fb0 --- /dev/null +++ b/generator/array_define.template.c @@ -0,0 +1,10 @@ +typedef struct +{ + uint32_t capacity; + uint32_t length; + $TYPE$ *internalArray; +} $NAME$; + +$NAME$ $NAME$_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return ($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), arena)}; +} \ No newline at end of file diff --git a/generator/array_get.template.c b/generator/array_get.template.c new file mode 100644 index 0000000..5aca709 --- /dev/null +++ b/generator/array_get.template.c @@ -0,0 +1,3 @@ +$TYPE$ *$NAME$_Get($NAME$ *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : $DEFAULT_VALUE$; +} \ No newline at end of file diff --git a/generator/array_remove_swapback.template.c b/generator/array_remove_swapback.template.c new file mode 100644 index 0000000..e87f51f --- /dev/null +++ b/generator/array_remove_swapback.template.c @@ -0,0 +1,9 @@ +$TYPE$ $NAME$_RemoveSwapback($NAME$ *array, int index) { + if (Clay__Array_RangeCheck(index, array->length)) { + array->length--; + $TYPE$ removed = array->internalArray[index]; + array->internalArray[index] = array->internalArray[array->length]; + return removed; + } + return $DEFAULT_VALUE$; +} \ No newline at end of file diff --git a/generator/array_set.template.c b/generator/array_set.template.c new file mode 100644 index 0000000..dd4c342 --- /dev/null +++ b/generator/array_set.template.c @@ -0,0 +1,11 @@ +void $NAME$_Set($NAME$ *array, int index, $TYPE$ value) { + if (index < array->capacity && index >= 0) { + array->internalArray[index] = value; + array->length = index < array->length ? array->length : index + 1; + } else { + Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow.")); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + } +} \ No newline at end of file diff --git a/generator/generate_templates.js b/generator/generate_templates.js new file mode 100644 index 0000000..9fec2d0 --- /dev/null +++ b/generator/generate_templates.js @@ -0,0 +1,69 @@ +const fs = require('fs'); +const path = require('path'); + +let files = ['../clay.h']; + +let templates = ['./']; +function readCTemplatesRecursive(directory) { + fs.readdirSync(directory).forEach(template => { + const absolute = path.join(directory, template); + if (fs.statSync(absolute).isDirectory()) return readCTemplatesRecursive(absolute); + else if (template.endsWith('template.c')) { + return templates.push(absolute); + } + }); +} + +readCTemplatesRecursive(__dirname); + +for (const file of files) { + const contents = fs.readFileSync(file, 'utf8'); + const lines = contents.split('\n'); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.startsWith('// __GENERATED__ template')) { + const [comment, generated, templateOpen, templateNames, ...args] = line.split(" "); + let matchingEndingLine = -1; + for (let j = i + 1; j < lines.length; j++) { + if (lines[j].startsWith('// __GENERATED__ template')) { + matchingEndingLine = j; + break; + } + } + if (matchingEndingLine !== -1) { + i++; + lines.splice(i, matchingEndingLine - (i)); + lines.splice(i, 0, ['#pragma region generated']); + i++; + for (const templateName of templateNames.split(',')) { + var matchingTemplate = templates.find(t => t.endsWith(`${templateName}.template.c`)); + if (matchingTemplate) { + let templateContents = fs.readFileSync(matchingTemplate, 'utf8'); + for (const arg of args) { + [argName, argValue] = arg.split('='); + templateContents = templateContents.replaceAll(`\$${argName}\$`, argValue); + } + let remainingTokens = templateContents.split('$'); + if (remainingTokens.length > 1) { + console.log(`Error at ${file}:${i}: Template is missing parameter ${remainingTokens[1]}`) + process.exit(); + } else { + templateContents = templateContents.split('\n'); + lines.splice(i, 0, ...templateContents); + i += templateContents.length; + } + } else { + console.log(`Error at ${file}:${i + 1}: no template with name ${templateName}.template.c was found.`); + process.exit(); + } + } + lines.splice(i, 0, ['#pragma endregion']); + i++; + } else { + console.log(`Error at ${file}:${i + 1}: template was opened and not closed again.`); + process.exit(); + } + } + } + fs.writeFileSync(file, lines.join('\n')); +} \ No newline at end of file diff --git a/renderers/raylib/clay_renderer_raylib.c b/renderers/raylib/clay_renderer_raylib.c new file mode 100644 index 0000000..8681660 --- /dev/null +++ b/renderers/raylib/clay_renderer_raylib.c @@ -0,0 +1,228 @@ +#include "raylib.h" +#include "raymath.h" +#include "stdint.h" +#include "string.h" +#include "stdio.h" +#include "stdlib.h" +#include "signal.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) } + +typedef struct +{ + uint32_t fontId; + Font font; +} Raylib_Font; + +Raylib_Font Raylib_fonts[10]; +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; + }; +} 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; +} + +uint32_t measureCalls = 0; + +static inline Clay_Dimensions Raylib_MeasureText(Clay_String *text, Clay_TextElementConfig *config) { + measureCalls++; + // Measure string size for Font + Clay_Dimensions textSize = { 0 }; + + float maxTextWidth = 0.0f; + float lineTextWidth = 0; + + float textHeight = config->fontSize; + Font fontToUse = Raylib_fonts[config->fontId].font; + + for (int i = 0; i < text->length; ++i) + { + if (text->chars[i] == '\n') { + maxTextWidth = fmax(maxTextWidth, lineTextWidth); + lineTextWidth = 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); + + textSize.width = maxTextWidth / 2; + textSize.height = textHeight; + + return textSize; +} + +void Clay_Raylib_Initialize(unsigned int flags) { + SetConfigFlags(flags); + InitWindow(1024, 768, "Clay - Raylib Renderer Example"); +// EnableEventWaiting(); +} + +void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands) +{ + measureCalls = 0; + for (int j = 0; j < renderCommands.length; j++) + { + Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); + Clay_Rectangle boundingBox = renderCommand->boundingBox; + switch (renderCommand->commandType) + { + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + // Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator + Clay_String text = renderCommand->text; + char *cloned = (char *)malloc(text.length + 1); + memcpy(cloned, text.chars, text.length); + cloned[text.length] = '\0'; + Font fontToUse = Raylib_fonts[renderCommand->config.textElementConfig->fontId].font; + DrawTextEx(fontToUse, cloned, (Vector2){boundingBox.x, boundingBox.y}, (float)renderCommand->config.textElementConfig->fontSize, (float)renderCommand->config.textElementConfig->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(renderCommand->config.textElementConfig->textColor)); + free(cloned); + break; + } + case CLAY_RENDER_COMMAND_TYPE_IMAGE: { + Texture2D imageTexture = *(Texture2D *)renderCommand->config.imageElementConfig->imageData; + DrawTextureEx( + imageTexture, + (Vector2){boundingBox.x, boundingBox.y}, + 0, + boundingBox.width / (float)imageTexture.width, + WHITE); + 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: { + DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width), (int)roundf(boundingBox.height), CLAY_COLOR_TO_RAYLIB_COLOR(renderCommand->config.rectangleElementConfig->color)); + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { + Clay_BorderContainerElementConfig *config = renderCommand->config.borderElementConfig; + // Left border + if (config->left.width > 0) { + DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y + config->cornerRadius.topLeft), (int)config->left.width, (int)roundf(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft), CLAY_COLOR_TO_RAYLIB_COLOR(config->left.color)); + } + // Right border + if (config->right.width > 0) { + DrawRectangle((int)roundf(boundingBox.x + boundingBox.width - config->right.width), (int)roundf(boundingBox.y + config->cornerRadius.topRight), (int)config->right.width, (int)roundf(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight), CLAY_COLOR_TO_RAYLIB_COLOR(config->right.color)); + } + // Top border + if (config->top.width > 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->top.width, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color)); + } + // Bottom border + if (config->bottom.width > 0) { + DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.bottomLeft), (int)roundf(boundingBox.y + boundingBox.height - config->bottom.width), (int)roundf(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight), (int)config->bottom.width, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.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->top.width), config->cornerRadius.topLeft, 180, 270, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.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->top.width), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.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->top.width), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.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->bottom.width), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color)); + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { + CustomLayoutElement *customElement = (CustomLayoutElement *)renderCommand->config.customElementConfig->customData; + if (!customElement) continue; + switch (customElement->type) { + case CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL: { + Clay_Rectangle 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->model.model, positionRay.position, customElement->model.scale * scaleValue, WHITE); // Draw 3d model with texture + EndMode3D(); + break; + } + default: break; + } + break; + } + default: { + printf("Error: unhandled render command."); + raise(SIGTRAP); + exit(1); + } + } + } +} \ No newline at end of file diff --git a/renderers/raylib/raylib.h b/renderers/raylib/raylib.h new file mode 100644 index 0000000..1cf34f0 --- /dev/null +++ b/renderers/raylib/raylib.h @@ -0,0 +1,1689 @@ +/********************************************************************************************** +* +* raylib v5.5-dev - 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 or ES2 - choose at compile) +* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Multiple Fonts formats supported (TTF, XNA fonts, AngelCode 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) +* - 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, 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] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input (PLATFORM_DESKTOP) +* [rlgl] glad (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading (PLATFORM_DESKTOP) +* [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 +* [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_resize (Sean Barret) for image resizing algorithms +* [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] Model3D (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) +* [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 +* +* +* 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-dev" + +// 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) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) + + // 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 +} 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 + CUBEMAP_LAYOUT_PANORAMA // Layout is defined by a panorama image (equirrectangular map) +} 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 (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowFocused(void); // Check if window is currently focused (only PLATFORM_DESKTOP) +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 (only PLATFORM_DESKTOP) +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) +RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed (only PLATFORM_DESKTOP) +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable (only PLATFORM_DESKTOP) +RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized (only PLATFORM_DESKTOP) +RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit, only PLATFORM_DESKTOP) +RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit, only PLATFORM_DESKTOP) +RLAPI void SetWindowTitle(const char *title); // Set title for window (only PLATFORM_DESKTOP and PLATFORM_WEB) +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen (only PLATFORM_DESKTOP) +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] (only PLATFORM_DESKTOP) +RLAPI void SetWindowFocused(void); // Set window focused (only PLATFORM_DESKTOP) +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 connected monitor +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 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 IsShaderReady(Shader shader); // Check if a shader is ready +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 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 +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() + +// 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 (Only PLATFORM_DESKTOP) +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); // Set gamepad vibration for both motors + +// 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 milliseconds +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 +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel (Vector version) +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 color1, Color color2); // 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 color1, Color color2);// Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // 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 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 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 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 CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] +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 LoadImageSvg(const char *fileNameOrString, int width, int height); // Load image from SVG file data or string with specified size +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 IsImageReady(Image image); // Check if an image is ready +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 IsTextureReady(Texture2D texture); // Check if a texture is ready +RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) +RLAPI bool IsRenderTextureReady(RenderTexture2D target); // Check if a render texture is ready +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 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 +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 IsFontReady(Font font); // Check if a font is ready +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 IsModelReady(Model model); // Check if a model is ready +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 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 IsMaterialReady(Material material); // Check if a material is ready +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 +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 IsWaveReady(Wave wave); // Checks if wave data is ready +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 IsSoundReady(Sound sound); // Checks if a sound is ready +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 IsMusicReady(Music music); // Checks if a music stream is ready +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 IsAudioStreamReady(AudioStream stream); // Checks if an audio stream is ready +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 diff --git a/renderers/raylib/raymath.h b/renderers/raylib/raymath.h new file mode 100644 index 0000000..4dc1552 --- /dev/null +++ b/renderers/raylib/raymath.h @@ -0,0 +1,2583 @@ +/********************************************************************************************** +* +* raymath v1.5 - 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 +* - 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. +* +* +* 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.m5 /= s.y; + clone.m10 /= s.z; + + // Extract rotation + *rotation = QuaternionFromMatrix(clone); + } + else + { + // Set to identity if close to zero + *rotation = QuaternionIdentity(); + } +} + +#endif // RAYMATH_H \ No newline at end of file diff --git a/renderers/web/build-wasm.sh b/renderers/web/build-wasm.sh new file mode 100755 index 0000000..adb93f8 --- /dev/null +++ b/renderers/web/build-wasm.sh @@ -0,0 +1,13 @@ +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; \ No newline at end of file diff --git a/renderers/web/canvas2d/clay-canvas2d-renderer.html b/renderers/web/canvas2d/clay-canvas2d-renderer.html new file mode 100644 index 0000000..ce895f6 --- /dev/null +++ b/renderers/web/canvas2d/clay-canvas2d-renderer.html @@ -0,0 +1,480 @@ + + + + + + + + Clay - UI Layout Library + + + + + + + + \ No newline at end of file diff --git a/renderers/web/clay.wasm b/renderers/web/clay.wasm new file mode 100755 index 0000000..5f04ebc Binary files /dev/null and b/renderers/web/clay.wasm differ diff --git a/renderers/web/html/clay-html-renderer.html b/renderers/web/html/clay-html-renderer.html new file mode 100644 index 0000000..7d23dd8 --- /dev/null +++ b/renderers/web/html/clay-html-renderer.html @@ -0,0 +1,522 @@ + + + + + + + + Clay - UI Layout Library + + + + + + + + \ No newline at end of file