From ee192f48c5458ddeb563f5eccd04f54848de6f70 Mon Sep 17 00:00:00 2001 From: Nic Barker Date: Tue, 31 Mar 2026 20:41:02 +1100 Subject: [PATCH] [Core] Transition API (#579) --- CMakeLists.txt | 1 + README.md | 679 ++++- clay.h | 2301 ++++++++++------- examples/cairo-pdf-rendering/main.c | 2 +- examples/clay-official-website/main.c | 8 +- examples/cpp-project-example/main.cpp | 2 +- .../raylib-sidebar-scrolling-container/main.c | 8 +- examples/raylib-transitions/CMakeLists.txt | 38 + examples/raylib-transitions/main.c | 422 +++ .../resources/Roboto-Regular.ttf | Bin 0 -> 168260 bytes examples/shared-layouts/clay-video-demo.c | 2 +- examples/sokol-corner-radius/main.c | 2 +- renderers/raylib/clay_renderer_raylib.c | 66 +- 13 files changed, 2454 insertions(+), 1077 deletions(-) create mode 100644 examples/raylib-transitions/CMakeLists.txt create mode 100644 examples/raylib-transitions/main.c create mode 100644 examples/raylib-transitions/resources/Roboto-Regular.ttf diff --git a/CMakeLists.txt b/CMakeLists.txt index 191b2f7..6ab1b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ endif () if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_RAYLIB_EXAMPLES) add_subdirectory("examples/raylib-multi-context") add_subdirectory("examples/raylib-sidebar-scrolling-container") + add_subdirectory("examples/raylib-transitions") endif () if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL2_EXAMPLES) add_subdirectory("examples/SDL2-video-demo") diff --git a/README.md b/README.md index 55226bf..d160f5e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ ### Major Features - Microsecond layout performance - Flex-box like layout model for complex, responsive layouts including text wrapping, scrolling containers and aspect ratio scaling -- Single ~4k LOC **clay.h** file with **zero** dependencies (including no standard library) +- Transition API for easy layout animations +- Single 4.8k LOC **clay.h** file with **zero** dependencies (including no standard library linking) - 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 @@ -91,8 +92,8 @@ int main() { .backgroundColor = COLOR_LIGHT }) { CLAY(CLAY_ID("ProfilePictureOuter"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = COLOR_RED }) { - CLAY(CLAY_ID("ProfilePicture"), {.layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture } }) {} - CLAY_TEXT(CLAY_STRING("Clay - UI Library"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255, 255, 255, 255} })); + CLAY(CLAY_ID("ProfilePicture"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture } }) {} + CLAY_TEXT(CLAY_STRING("Clay - UI Library"), { .fontSize = 24, .textColor = {255, 255, 255, 255} }); } // Standard C code like loops etc work inside components @@ -105,7 +106,7 @@ int main() { } // All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout - Clay_RenderCommandArray renderCommands = Clay_EndLayout(); + Clay_RenderCommandArray renderCommands = Clay_EndLayout(deltaTime); // deltaTime is the time since the last frame, and is used for transitions // More comprehensive rendering examples can be found in the renderers/ directory for (int i = 0; i < renderCommands.length; i++) { @@ -113,7 +114,7 @@ int main() { switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { - DrawRectangle( renderCommand->boundingBox, renderCommand->renderData.rectangle.backgroundColor); + DrawRectangle(renderCommand->boundingBox, renderCommand->renderData.rectangle.backgroundColor); } // ... Implement handling of other command types } @@ -140,55 +141,83 @@ For help starting out or to discuss clay, considering joining [the discord serve ## Summary -- [High Level Documentation](#high-level-documentation) - - [Building UI Hierarchies](#building-ui-hierarchies) - - [Configuring Layout and Styling UI Elements](#configuring-layout-and-styling-ui-elements) - - [Element IDs](#element-ids) - - [Mouse, Touch and Pointer Interactions](#mouse-touch-and-pointer-interactions) - - [Scrolling Elements](#scrolling-elements) - - [Floating Elements](#floating-elements-absolute-positioning) - - [Custom Elements](#laying-out-your-own-custom-elements) - - [Retained Mode Rendering](#retained-mode-rendering) - - [Visibility Culling](#visibility-culling) - - [Preprocessor Directives](#preprocessor-directives) - - [Bindings](#bindings-for-non-c) - - [Debug Tools](#debug-tools) -- [API](#api) - - [Naming Conventions](#naming-conventions) - - [Public Functions](#public-functions) - - [Lifecycle](#lifecycle-for-public-functions) - - [Clay_MinMemorySize](#clay_minmemorysize) - - [Clay_CreateArenaWithCapacityAndMemory](#clay_createarenawithcapacityandmemory) - - [Clay_SetMeasureTextFunction](#clay_setmeasuretextfunction) - - [Clay_ResetMeasureTextCache](#clay_resetmeasuretextcache) - - [Clay_SetMaxElementCount](#clay_setmaxelementcount) - - [Clay_SetMaxMeasureTextCacheWordCount](#clay_setmaxmeasuretextcachewordcount) - - [Clay_Initialize](#clay_initialize) - - [Clay_GetCurrentContext](#clay_getcurrentcontext) - - [Clay_SetCurrentContext](#clay_setcurrentcontext) - - [Clay_SetLayoutDimensions](#clay_setlayoutdimensions) - - [Clay_SetPointerState](#clay_setpointerstate) - - [Clay_UpdateScrollContainers](#clay_updatescrollcontainers) - - [Clay_BeginLayout](#clay_beginlayout) - - [Clay_EndLayout](#clay_endlayout) - - [Clay_Hovered](#clay_hovered) - - [Clay_OnHover](#clay_onhover) - - [Clay_PointerOver](#clay_pointerover) - - [Clay_GetScrollContainerData](#clay_getscrollcontainerdata) - - [Clay_GetElementData](#clay_getelementdata) - - [Clay_GetElementId](#clay_getelementid) - - [Element Macros](#element-macros) - - [CLAY](#clay) - - [CLAY_ID](#clay_id) - - [CLAY_IDI](#clay_idi) - - [Data Structures & Defs](#data-structures--definitions) - - [Clay_String](#clay_string) - - [Clay_ElementId](#clay_elementid) - - [Clay_RenderCommandArray](#clay_rendercommandarray) - - [Clay_RenderCommand](#clay_rendercommand) - - [Clay_ScrollContainerData](#clay_scrollcontainerdata) - - [Clay_ErrorHandler](#clay_errorhandler) - - [Clay_ErrorData](#clay_errordata) + +* [High Level Documentation](#high-level-documentation) + * [Building UI Hierarchies](#building-ui-hierarchies) + * [Configuring Layout and Styling UI Elements](#configuring-layout-and-styling-ui-elements) + * [Element IDs](#element-ids) + * [Mouse, Touch and Pointer Interactions](#mouse-touch-and-pointer-interactions) + * [Scrolling Elements](#scrolling-elements) + * [Floating Elements ("Absolute" Positioning)](#floating-elements-absolute-positioning) + * [Laying Out Your Own Custom Elements](#laying-out-your-own-custom-elements) + * [Transitions](#transitions) + * [Retained Mode Rendering](#retained-mode-rendering) + * [Visibility Culling](#visibility-culling) + * [Preprocessor Directives](#preprocessor-directives) + * [Bindings for non C](#bindings-for-non-c) + * [Other implementations](#other-implementations) + * [Debug Tools](#debug-tools) + * [Running more than one Clay instance](#running-more-than-one-clay-instance) +* [API](#api) + * [Naming Conventions](#naming-conventions) + * [Public Functions](#public-functions) + * [Lifecycle for public functions](#lifecycle-for-public-functions) + * [Clay_MinMemorySize](#clay_minmemorysize) + * [Clay_CreateArenaWithCapacityAndMemory](#clay_createarenawithcapacityandmemory) + * [Clay_SetMeasureTextFunction](#clay_setmeasuretextfunction) + * [Clay_ResetMeasureTextCache](#clay_resetmeasuretextcache) + * [Clay_SetMaxElementCount](#clay_setmaxelementcount) + * [Clay_SetMaxMeasureTextCacheWordCount](#clay_setmaxmeasuretextcachewordcount) + * [Clay_Initialize](#clay_initialize) + * [Clay_SetCurrentContext](#clay_setcurrentcontext) + * [Clay_GetCurrentContext](#clay_getcurrentcontext) + * [Clay_SetLayoutDimensions](#clay_setlayoutdimensions) + * [Clay_SetPointerState](#clay_setpointerstate) + * [Clay_UpdateScrollContainers](#clay_updatescrollcontainers) + * [Clay_GetScrollOffset](#clay_getscrolloffset) + * [Clay_BeginLayout](#clay_beginlayout) + * [Clay_EndLayout](#clay_endlayout) + * [Clay_Hovered](#clay_hovered) + * [Clay_OnHover](#clay_onhover) + * [Clay_PointerOver](#clay_pointerover) + * [Clay_GetOpenElementId](#clay_getopenelementid) + * [Clay_GetScrollContainerData](#clay_getscrollcontainerdata) + * [Clay_GetElementData](#clay_getelementdata) + * [Clay_GetElementId](#clay_getelementid) + * [Element Macros](#element-macros) + * [CLAY()](#clay) + * [CLAY_AUTO_ID()](#clay_auto_id) + * [CLAY_TEXT()](#clay_text) + * [CLAY_ID()](#clay_id) + * [CLAY_SID()](#clay_sid) + * [CLAY_IDI()](#clay_idi) + * [CLAY_SIDI()](#clay_sidi) + * [CLAY_ID_LOCAL()](#clay_id_local) + * [CLAY_SID_LOCAL()](#clay_sid_local) + * [CLAY_IDI_LOCAL()](#clay_idi_local) + * [CLAY_SIDI_LOCAL()](#clay_sidi_local) + * [Data Structures & Definitions](#data-structures--definitions) + * [Clay_ElementDeclaration](#clay_elementdeclaration) + * [Clay_LayoutConfig](#clay_layoutconfig) + * [Clay_ImageElementConfig](#clay_imageelementconfig) + * [Clay_AspectRatioElementConfig](#clay_aspectratioelementconfig) + * [Clay_ImageElementConfig](#clay_imageelementconfig-1) + * [Clay_ClipElementConfig](#clay_clipelementconfig) + * [Clay_BorderElementConfig](#clay_borderelementconfig) + * [Clay_FloatingElementConfig](#clay_floatingelementconfig) + * [Clay_CustomElementConfig](#clay_customelementconfig) + * [Clay_TransitionElementConfig](#clay_transitionelementconfig) + * [Clay_Color](#clay_color) + * [Clay_String](#clay_string) + * [Clay_ElementId](#clay_elementid) + * [Clay_RenderCommandArray](#clay_rendercommandarray) + * [Clay_RenderCommand](#clay_rendercommand) + * [Clay_ScrollContainerData](#clay_scrollcontainerdata) + * [Clay_ElementData](#clay_elementdata) + * [Clay_PointerData](#clay_pointerdata) + * [Clay_ErrorHandler](#clay_errorhandler) + * [Clay_ErrorData](#clay_errordata) + ## High Level Documentation @@ -200,7 +229,7 @@ Child elements are added by opening a block: `{}` after calling the `CLAY()` mac // Parent element with 8px of padding CLAY(CLAY_ID("parent"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) { // Child element 1 - CLAY_TEXT(CLAY_STRING("Hello World"), CLAY_TEXT_CONFIG({ .fontSize = 16 })); + CLAY_TEXT(CLAY_STRING("Hello World"), { .fontSize = 16 }); // Child element 2 with red background CLAY((CLAY_ID("child"), { .backgroundColor = COLOR_RED }) { // etc @@ -332,7 +361,7 @@ If you want to query mouse / pointer overlaps outside layout declarations, you c ```C // Reminder: Clay_SetPointerState must be called before functions that rely on pointer position otherwise it will have no effect Clay_Vector2 mousePosition = { x, y }; -Clay_SetPointerState(mousePosition); +Clay_SetPointerState(mousePosition, mouseButtonDown(0)); // ... // If profile picture was clicked if (mouseButtonDown(0) && Clay_PointerOver(Clay_GetElementId("ProfilePicture"))) { @@ -385,7 +414,7 @@ A classic example use case for floating elements is tooltips and modals. ```C // The two text elements will be laid out top to bottom, and the floating container // will be attached to "Outer" -CLAY(CLAY_ID("Outer"), { .layout = { .layoutDirection = TOP_TO_BOTTOM } }) { +CLAY(CLAY_ID("Outer"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { CLAY_TEXT(text, &headerTextConfig); CLAY(CLAY_ID("Tooltip"), { .floating = { .attachTo = CLAY_ATTACH_TO_PARENT } }) {} CLAY_TEXT(text, &headerTextConfig); @@ -457,6 +486,40 @@ switch (renderCommand->commandType) { More specific details can be found in the full [Custom Element API](#clay_customelementconfig). +### Transitions +Clay includes a "Transition" API, which allows you to smoothly animate / tween from one state to another. +Both layout-affecting properties such as `width` and `height`, as well as non-layout properties such as `backgroundColor` are supported. +See the [transition documentation]() for more info. + +```C +// Note: for transitions to work, elements need a stable ID from one frame to the next - using loop indexes or CLAY_AUTO_ID will not work. +CLAY(CLAY_IDI("box", colors[index].id), { + .layout.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, + .layout.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }, + .backgroundColor = boxColor, + .overlayColor = Clay_Hovered() ? (Clay_Color) { 140, 140, 140, 80 } : (Clay_Color) { 255, 255, 255, 0 }, + // Transitions will activate once a handler function is defined. + .transition = { + .handler = Clay_EaseOut, + .duration = 0.5f, + // A "flag" enum is used to specify which properties to transition, use a bitwise OR (|) to construct the flags. + .properties = CLAY_TRANSITION_PROPERTY_WIDTH | CLAY_TRANSITION_PROPERTY_POSITION | CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR | CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR, + .enter = { .setInitialState = EnterExitSlideUp }, + .exit = { .setFinalState = EnterExitSlideUp }, + } +}) { + Clay_OnHover(HandleCellButtonInteraction, (void*)(uint64_t)index); + CLAY_TEXT(((Clay_String) { .length = 2, .chars = colors[index].stringId, .isStaticallyAllocated = true }), { + .fontSize = 32, + .textColor = colors[index].id > 29 ? (Clay_Color) {255, 255, 255, 255} : (Clay_Color) {154, 123, 184, 255 } + }); +} +``` + + + +_An example of the transition API action can be found at examples/raylib-transitions_ + ### 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. @@ -528,14 +591,14 @@ Clay_Context* instance2 = Clay_Initialize(arena2, layoutDimensions, errorHandler Clay_SetCurrentContext(instance1); Clay_BeginLayout(); // ... declare layout for instance1 -Clay_RenderCommandArray renderCommands1 = Clay_EndLayout(); +Clay_RenderCommandArray renderCommands1 = Clay_EndLayout(deltaTime); render(renderCommands1); // Switch to the second instance Clay_SetCurrentContext(instance2); Clay_BeginLayout(); // ... declare layout for instance2 -Clay_RenderCommandArray renderCommands2 = Clay_EndLayout(); +Clay_RenderCommandArray renderCommands2 = Clay_EndLayout(deltaTime); render(renderCommands2); ``` @@ -569,7 +632,7 @@ Returns the minimum amount of memory **in bytes** that clay needs to accommodate ### Clay_CreateArenaWithCapacityAndMemory -`Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *offset)` +`Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory)` Creates a `Clay_Arena` struct with the given capacity and base memory pointer, which can be passed to [Clay_Initialize](#clay_initialize). @@ -577,7 +640,7 @@ Creates a `Clay_Arena` struct with the given capacity and base memory pointer, w ### Clay_SetMeasureTextFunction -`void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData), uintptr_t userData)` +`void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void* userData), void* userData)` Takes a pointer to a function that can be used to measure the `width, height` dimensions of a string. Used by clay during layout to determine [CLAY_TEXT](#clay_text) element sizing and wrapping. @@ -597,7 +660,7 @@ Clay caches measurements from the provided MeasureTextFunction, and this will be ### Clay_SetMaxElementCount -`void Clay_SetMaxElementCount(uint32_t maxElementCount)` +`void Clay_SetMaxElementCount(int32_t maxElementCount)` Sets the internal maximum element count that will be used in subsequent [Clay_Initialize()](#clay_initialize) and [Clay_MinMemorySize()](#clay_minmemorysize) calls, allowing clay to allocate larger UI hierarchies. @@ -607,7 +670,7 @@ Sets the internal maximum element count that will be used in subsequent [Clay_In ### Clay_SetMaxMeasureTextCacheWordCount -`void Clay_SetMaxMeasureTextCacheWordCount(uint32_t maxMeasureTextCacheWordCount)` +`void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount)` Sets the internal text measurement cache size that will be used in subsequent [Clay_Initialize()](#clay_initialize) and [Clay_MinMemorySize()](#clay_minmemorysize) calls, allowing clay to allocate more text. The value represents how many separate words can be stored in the text measurement cache. @@ -745,6 +808,17 @@ CLAY(CLAY_ID("Button"), { .layout = { .padding = CLAY_PADDING_ALL(8) } }) { Returns `true` if the pointer position previously set with `Clay_SetPointerState` is inside the bounding box of the layout element with the provided `id`. Note: this is based on the element's position from the **last** frame. If frame-accurate pointer overlap detection is required, perhaps in the case of significant change in UI layout between frames, you can simply run your layout code twice that frame. The second call to `Clay_PointerOver` will be frame-accurate. +--- + +### Clay_GetOpenElementId + +`Clay_ElementId Clay_GetOpenElementId()` + +Returns the [Clay_ElementId](#clay_elementid) of the currently open element. Useful for getting the ID of elements opened with [CLAY_AUTO_ID](#clay_auto_id). + +--- + + ### Clay_GetScrollContainerData `Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id)` @@ -851,7 +925,7 @@ Note that `Clay_TextElementConfig` uses `uint32_t fontId`. Font ID to font asset **Struct API (Pseudocode)** ```C -// CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .member = value })) supports these options +// CLAY_TEXT(text, { .member = value }) supports these options Clay_TextElementConfig { Clay_Color textColor { float r; float g; float b; float a; @@ -865,6 +939,12 @@ Clay_TextElementConfig { CLAY_TEXT_WRAP_NEWLINES, CLAY_TEXT_WRAP_NONE, }; + Clay_TextAlignment textAlignment { + CLAY_TEXT_ALIGN_LEFT (default), + CLAY_TEXT_ALIGN_CENTER, + CLAY_TEXT_ALIGN_RIGHT, + }; + void *userData; }; ``` @@ -872,7 +952,7 @@ Clay_TextElementConfig { **`.textColor`** -`CLAY_TEXT_CONFIG(.textColor = {120, 120, 120, 255})` +`CLAY_TEXT(text, { .textColor = {120, 120, 120, 255} })` Uses [Clay_Color](#clay_color). Conventionally accepts `rgba` float values between 0 and 255, but interpretation is left up to the renderer and does not affect layout. @@ -880,7 +960,7 @@ Uses [Clay_Color](#clay_color). Conventionally accepts `rgba` float values betwe **`.fontId`** -`CLAY_TEXT_CONFIG(.fontId = FONT_ID_LATO)` +`CLAY_TEXT(text, { .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. @@ -888,7 +968,7 @@ It's up to the user to load fonts and create a mapping from `fontId` to a font t **`.fontSize`** -`CLAY_TEXT_CONFIG(.fontSize = 16)` +`CLAY_TEXT(text, { .fontSize = 16 })` Font size is generally thought of as `x pixels tall`, but interpretation is left up to the user & renderer. @@ -896,7 +976,7 @@ Font size is generally thought of as `x pixels tall`, but interpretation is left **`.letterSpacing`** -`CLAY_TEXT_CONFIG(.letterSpacing = 1)` +`CLAY_TEXT(text, { .letterSpacing = 1 })` `.letterSpacing` results in **horizontal** white space between individual rendered characters. @@ -904,7 +984,7 @@ Font size is generally thought of as `x pixels tall`, but interpretation is left **`.lineHeight`** -`CLAY_TEXT_CONFIG(.lineHeight = 20)` +`CLAY_TEXT(text, { .lineHeight = 20 })` `.lineHeight` - when non zero - forcibly sets the `height` of each wrapped line of text to `.lineheight` pixels tall. Will affect the layout of both parents and siblings. A value of `0` will use the measured height of the font. @@ -912,7 +992,7 @@ Font size is generally thought of as `x pixels tall`, but interpretation is left **`.wrapMode`** -`CLAY_TEXT_CONFIG(.wrapMode = CLAY_TEXT_WRAP_NONE)` +`CLAY_TEXT(text, { .wrapMode = CLAY_TEXT_WRAP_NONE })` `.wrapMode` specifies under what conditions text should [wrap](https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap). @@ -924,15 +1004,29 @@ Available options are: --- +**`.textAlignment`** + +`CLAY_TEXT(text, { .textAlignment = CLAY_TEXT_ALIGN_CENTER })` + +`.textAlignment` controls how **wrapping** text lines are aligned. If you want to control the alignment of single lines of text, instead use the `childAlignment` property of the **parent** layout element. + +Available options are: + +- `CLAY_TEXT_ALIGN_LEFT` (default) +- `CLAY_TEXT_ALIGN_CENTER` +- `CLAY_TEXT_ALIGN_RIGHT` + +--- + **Examples** ```C // Define a font somewhere in your code const uint32_t FONT_ID_LATO = 3; // .. -CLAY_TEXT(CLAY_STRING("John Smith"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_LATO, .fontSize = 24, .textColor = {255, 0, 0, 255} })); +CLAY_TEXT(CLAY_STRING("John Smith"), { .fontId = FONT_ID_LATO, .fontSize = 24, .textColor = {255, 0, 0, 255} }); // Rendering example -Font fontToUse = LoadedFonts[renderCommand->renderData.text->fontId]; +Font fontToUse = LoadedFonts[renderCommand->renderData.text.fontId]; ``` **Rendering** @@ -943,7 +1037,7 @@ Element is subject to [culling](#visibility-culling). Otherwise, multiple `Clay_ --- -### CLAY_ID +### CLAY_ID() `Clay_ElementId CLAY_ID(STRING_LITERAL idString)` @@ -964,7 +1058,7 @@ CLAY(CLAY_ID("Button"), { } // Later on outside of layout code -bool buttonIsHovered = Clay_IsPointerOver(Clay_GetElementId("Button")); +bool buttonIsHovered = Clay_PointerOver(Clay_GetElementId("Button")); if (buttonIsHovered && leftMouseButtonPressed) { // ... do some click handling } @@ -1074,7 +1168,6 @@ The **Clay_ElementDeclaration** struct is the only argument to the `CLAY()` macr ```C typedef struct { - Clay_ElementId id; Clay_LayoutConfig layout; Clay_Color backgroundColor; Clay_CornerRadius cornerRadius; @@ -1100,13 +1193,28 @@ Uses [Clay_LayoutConfig](#clay_layoutconfig). Controls various settings related **`.backgroundColor`** - `Clay_Color` -`CLAY(CLAY_ID("Element"), { .backgroundColor = {120, 120, 120, 255} } })` +`CLAY(CLAY_ID("Element"), { .backgroundColor = { 120, 120, 120, 255 } } })` Uses [Clay_Color](#clay_color). Conventionally accepts `rgba` float values between 0 and 255, but interpretation is left up to the renderer and does not affect layout. --- -**`.cornerRadius`** - `float` +**`.overlayColor`** - `Clay_Color` + +`CLAY(CLAY_ID("Element"), { .overlayColor = { 255, 120, 120, 255 } } })` + +Uses [Clay_Color](#clay_color). Conventionally accepts `rgba` float values between 0 and 255, but interpretation is left up to the renderer and does not affect layout. + +Specifying `.overlayColor` will cause two new render commands to be emitted: `OVERLAY_COLOR_BEGIN` immediately, and `OVERLAY_COLOR_END` after this elements subtree has been emitted. + +This instructs the renderer to begin applying a color overlay to this element, and all child elements. The color overlay effect is similar to glsl's `mix(source, target, alpha)`. As a result, the strength of the color overlay is controlled by the `.a` channel of `.overlayColor`. +Zero alpha means "all child colors remain the same", full alpha means "all child colours are replaced by the RGB overlayColor", and values in between mean "lerp from the child color to the overlay color by the alpha". + +This can be used as a cheap replacement for alpha / opacity to "fade in / out", by applying (and potentially transitioning) an overlay color to the same color as the background with full alpha. + +--- + +**`.cornerRadius`** - `Clay_CornerRadius` `CLAY(CLAY_ID("Element"), { .cornerRadius = { .topLeft = 16, .topRight = 16, .bottomLeft = 16, .bottomRight = 16 } })` @@ -1152,7 +1260,7 @@ Uses [Clay_CustomElementConfig](#clay_customelementconfig). Configures the eleme `CLAY(CLAY_ID("Element"), { .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } })` -Uses [Clay_ClipElementConfig](#clay_scrollelementconfig). Configures the element as a clip element, which causes child elements to be clipped / masked if they overflow, and together with the functions listed in [Scrolling Elements](#scrolling-elements) enables scrolling of child contents. +Uses [Clay_ClipElementConfig](#clay_cliplelementconfig). Configures the element as a clip element, which causes child elements to be clipped / masked if they overflow, and together with the functions listed in [Scrolling Elements](#scrolling-elements) enables scrolling of child contents. An image demonstrating the concept of clipping which prevents rendering of a child elements pixels if they fall outside the bounds of the parent element. @@ -1195,7 +1303,7 @@ CLAY(CLAY_ID("ScrollingContainer"), { } ``` -Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE` will be created, with `renderCommand->elementConfig.rectangleElementConfig` containing a pointer to the element's Clay_RectangleElementConfig. +Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE` will be created, with `renderCommand->renderData.rectangle` containing a pointer to the element's Clay_RectangleElementConfig. ### Clay_LayoutConfig @@ -1346,7 +1454,7 @@ YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imag **Rendering** -Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE` will be created. The user will need to access `renderCommand->renderData.image->imageData` to retrieve image data referenced during layout creation. It's also up to the user to decide how / if they wish to blend `renderCommand->renderData.image->backgroundColor` with the image. +Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE` will be created. The user will need to access `renderCommand->renderData.image.imageData` to retrieve image data referenced during layout creation. It's also up to the user to decide how / if they wish to blend `renderCommand->renderData.image.backgroundColor` with the image. --- @@ -1440,7 +1548,7 @@ YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imag **Rendering** -Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE` will be created. The user will need to access `renderCommand->renderData.image->imageData` to retrieve image data referenced during layout creation. It's also up to the user to decide how / if they wish to blend `renderCommand->renderData.image->backgroundColor` with the image. +Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE` will be created. The user will need to access `renderCommand->renderData.image.imageData` to retrieve image data referenced during layout creation. It's also up to the user to decide how / if they wish to blend `renderCommand->renderData.image.backgroundColor` with the image. --- @@ -1462,6 +1570,7 @@ Note: In order to process scrolling based on pointer position and mouse wheel or Clay_ClipElementConfig { bool horizontal; bool vertical; + Clay_Vector2 childOffset; }; ``` @@ -1483,6 +1592,14 @@ Enables or disables vertical clipping for this container element. --- +**`.childOffset`** - `Clay_Vector2` + +`CLAY(LAY_ID("VerticalScroll"), { .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } })` + +Controls the x/y offset for child elements of this clip container. Used to control scrolling. You can either provide the vector manually if you want to manage scrolling yourself, or you can use the built in [Clay_GetScrollOffset](#clay_getscrolloffset) function which will manage scrolling for you automatically. + +--- + **Rendering** Enabling clip for an element will result in two additional render commands: @@ -1628,11 +1745,11 @@ Clay_FloatingElementConfig { }; uint32_t parentId; int16_t zIndex; - Clay_FloatingAttachPoints attachPoint { + Clay_FloatingAttachPoints attachPoints { .element = CLAY_ATTACH_POINT_LEFT_TOP (default) | CLAY_ATTACH_POINT_LEFT_CENTER | CLAY_ATTACH_POINT_LEFT_BOTTOM | CLAY_ATTACH_POINT_CENTER_TOP | CLAY_ATTACH_POINT_CENTER_CENTER | CLAY_ATTACH_POINT_CENTER_BOTTOM | CLAY_ATTACH_POINT_RIGHT_TOP | CLAY_ATTACH_POINT_RIGHT_CENTER | CLAY_ATTACH_POINT_RIGHT_BOTTOM .parent = CLAY_ATTACH_POINT_LEFT_TOP (default) | CLAY_ATTACH_POINT_LEFT_CENTER | CLAY_ATTACH_POINT_LEFT_BOTTOM | CLAY_ATTACH_POINT_CENTER_TOP | CLAY_ATTACH_POINT_CENTER_CENTER | CLAY_ATTACH_POINT_CENTER_BOTTOM | CLAY_ATTACH_POINT_RIGHT_TOP | CLAY_ATTACH_POINT_RIGHT_CENTER | CLAY_ATTACH_POINT_RIGHT_BOTTOM }; - Clay_FloatingAttachToElement attachTo { + Clay_PointerCaptureMode pointerCaptureMode { CLAY_POINTER_CAPTURE_MODE_CAPTURE (default), CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH }; @@ -1642,6 +1759,10 @@ Clay_FloatingElementConfig { CLAY_ATTACH_TO_ELEMENT_WITH_ID, CLAY_ATTACH_TO_ROOT, }; + Clay_FloatingClipToElement clipTo { + CLAY_CLIP_TO_NONE (default), + CLAY_CLIP_TO_ATTACHED_PARENT, + }; }; ``` @@ -1663,7 +1784,7 @@ Used to expand the width and height of the floating container _before_ laying ou --- -**`.zIndex`** - `float` +**`.zIndex`** - `int16_t` `CLAY(CLAY_ID("Floating"), { .floating = { .zIndex = 1 } })` @@ -1733,18 +1854,18 @@ CLAY(CLAY_IDI("SidebarButton", 5), { }) { } // Any other point in the hierarchy -CLAY(CLAY_ID("OptionTooltip"), { .floating = { .attachTo = CLAY_ATTACH_TO_ELEMENT_ID, .parentId = CLAY_IDI("SidebarButton", tooltip.attachedButtonIndex).id }) { +CLAY(CLAY_ID("OptionTooltip"), { .floating = { .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, .parentId = CLAY_IDI("SidebarButton", tooltip.attachedButtonIndex).id }) { // Tooltip contents... } ``` --- -**`.attachment`** - `Clay_FloatingAttachPoints` +**`.attachPoints`** - `Clay_FloatingAttachPoints` -`CLAY(CLAY_ID("Floating"), { .floating = { .attachment = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } }) {}` +`CLAY(CLAY_ID("Floating"), { .floating = { .attachPoints = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } }) {}` -In terms of positioning the floating container, `.attachment` specifies +In terms of positioning the floating container, `.attachPoints` specifies - The point on the floating container (`.element`) - The point on the parent element that it "attaches" to (`.parent`) @@ -1757,33 +1878,50 @@ For example: "Attach the LEFT_CENTER of the floating container to the RIGHT_TOP of the parent" -`CLAY(CLAY_ID("Floating"), { .floating = { .attachment = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } });` +`CLAY(CLAY_ID("Floating"), { .floating = { .attachPoints = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_TOP } } });` ![Screenshot 2024-08-23 at 11 53 24 AM](https://github.com/user-attachments/assets/ebe75e0d-1904-46b0-982d-418f929d1516) +--- + **`.pointerCaptureMode`** - `Clay_PointerCaptureMode` `CLAY({ .floating = { .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_CAPTURE } })` Controls whether pointer events like hover and click should pass through to content underneath this floating element, or whether the pointer should be "captured" by this floating element. Defaults to `CLAY_POINTER_CAPTURE_MODE_CAPTURE`. +--- + +**`.clipTo`** - `Clay_FloatingClipToElement` + +`CLAY({ .floating = { .clipTo = CLAY_CLIP_TO_ATTACHED_PARENT } })` + +By default, floating elements will appear with a z-order **above** their parent, and won't be clipped by a `.clip` defined on that parent. To clip floating elements by their parents' clip rectangle, use `.clipTo = CLAY_CLIP_TO_ATTACHED_PARENT`. + +```C +typedef enum { + CLAY_CLIP_TO_NONE, + CLAY_CLIP_TO_ATTACHED_PARENT, +} Clay_FloatingClipToElement; +``` + **Examples** ```C // Horizontal container with three option buttons CLAY(CLAY_ID("OptionsList"), { .layout = { childGap = 16 } }) { CLAY(CLAY_IDI("Option", 1), { .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) { - CLAY_TEXT(CLAY_STRING("Option 1"), CLAY_TEXT_CONFIG()); + CLAY_TEXT(CLAY_STRING("Option 1"), {}); } CLAY(CLAY_IDI("Option", 2), { .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) { - CLAY_TEXT(CLAY_STRING("Option 2"), CLAY_TEXT_CONFIG()); + CLAY_TEXT(CLAY_STRING("Option 2"), {}); // Floating tooltip will attach above the "Option 2" container and not affect widths or positions of other elements - CLAY(CLAY_ID("OptionTooltip"), { .floating = { .zIndex = 1, .attachment = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_CENTER_TOP } } }) { - CLAY_TEXT(CLAY_STRING("Most popular!"), CLAY_TEXT_CONFIG()); + CLAY(CLAY_ID("OptionTooltip"), { .floating = { .zIndex = 1, .attachPoints = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_CENTER_TOP } } }) { + CLAY_TEXT(CLAY_STRING("Most popular!"), {}); } } CLAY(CLAY_IDI("Option", 3), { .layout = { padding = CLAY_PADDING_ALL(16)), .backgroundColor = COLOR_BLUE } }) { - CLAY_TEXT(CLAY_STRING("Option 3"), CLAY_TEXT_CONFIG()); + CLAY_TEXT(CLAY_STRING("Option 3"), {}); } } @@ -1795,8 +1933,8 @@ for (int i = 0; i < 1000; i++) { } // Note the use of "parentId". // Floating tooltip will attach above the "Option 2" container and not affect widths or positions of other elements -CLAY(CLAY_ID("OptionTooltip"), { .floating = { .parentId = CLAY_IDI("Option", 2).id, .zIndex = 1, .attachment = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_TOP_CENTER } } }) { - CLAY_TEXT(CLAY_STRING("Most popular!"), CLAY_TEXT_CONFIG()); +CLAY(CLAY_ID("OptionTooltip"), { .floating = { .parentId = CLAY_IDI("Option", 2).id, .zIndex = 1, .attachPoints = { .element = CLAY_ATTACH_POINT_CENTER_BOTTOM, .parent = CLAY_ATTACH_POINT_TOP_CENTER } } }) { + CLAY_TEXT(CLAY_STRING("Most popular!"), {}); } ``` @@ -1901,6 +2039,323 @@ switch (renderCommand->commandType) { Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand` with `commandType = CLAY_RENDER_COMMAND_TYPE_CUSTOM` will be created. +### Clay_TransitionElementConfig + +**Usage** + +`CLAY(CLAY_ID("Transition"), { .transition = { ...transition config } }) {}` + +**Notes** + +`Clay_TransitionElementConfig` is used to configure element "transitions", which are animations between states. +A `.handler` function and `.properties` must be provided for transitions to occur. + +**Struct Definition (Pseudocode)** + +```C +typedef struct Clay_TransitionElementConfig +{ + // Handler function pointer for computing current frame state, see below for more info + bool (*handler)(Clay_TransitionCallbackArguments arguments); + float duration; + // Note: this is a flags field. You can pass multiple properties using a bitwise OR, e.g. CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR | CLAY_TRANSITION_PROPERTY_CORNER_RADIUS + Clay_TransitionProperty properties = { + CLAY_TRANSITION_PROPERTY_NONE (default), + CLAY_TRANSITION_PROPERTY_X, + CLAY_TRANSITION_PROPERTY_Y, + CLAY_TRANSITION_PROPERTY_POSITION = CLAY_TRANSITION_PROPERTY_X | CLAY_TRANSITION_PROPERTY_Y, + CLAY_TRANSITION_PROPERTY_WIDTH, + CLAY_TRANSITION_PROPERTY_HEIGHT, + CLAY_TRANSITION_PROPERTY_DIMENSIONS = CLAY_TRANSITION_PROPERTY_WIDTH | CLAY_TRANSITION_PROPERTY_HEIGHT, + CLAY_TRANSITION_PROPERTY_BOUNDING_BOX = CLAY_TRANSITION_PROPERTY_POSITION | CLAY_TRANSITION_PROPERTY_DIMENSIONS, + CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR, + CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR, + CLAY_TRANSITION_PROPERTY_CORNER_RADIUS, + CLAY_TRANSITION_PROPERTY_BORDER_COLOR, + CLAY_TRANSITION_PROPERTY_BORDER_WIDTH, + CLAY_TRANSITION_PROPERTY_BORDER = CLAY_TRANSITION_PROPERTY_BORDER_COLOR | CLAY_TRANSITION_PROPERTY_BORDER_WIDTH + }; + Clay_TransitionInteractionHandlingType interactionHandling { + CLAY_TRANSITION_DISABLE_INTERACTIONS_WHILE_TRANSITIONING_POSITION (default), + CLAY_TRANSITION_ALLOW_INTERACTIONS_WHILE_TRANSITIONING_POSITION, + }; + struct { + // Function pointer, see below for details + Clay_TransitionData (*setInitialState)(Clay_TransitionData targetState, Clay_TransitionProperty properties); + Clay_TransitionEnterTriggerType trigger { + CLAY_TRANSITION_ENTER_SKIP_ON_FIRST_PARENT_FRAME (default), + CLAY_TRANSITION_ENTER_TRIGGER_ON_FIRST_PARENT_FRAME, + }; + } enter; + struct { + Clay_TransitionData (*setFinalState)(Clay_TransitionData initialState, Clay_TransitionProperty properties); + Clay_TransitionExitTriggerType trigger { + CLAY_TRANSITION_EXIT_SKIP_WHEN_PARENT_EXITS (default), + CLAY_TRANSITION_EXIT_TRIGGER_WHEN_PARENT_EXITS, + }; + Clay_ExitTransitionSiblingOrdering siblingOrdering { + CLAY_EXIT_TRANSITION_ORDERING_UNDERNEATH_SIBLINGS (default), + CLAY_EXIT_TRANSITION_ORDERING_NATURAL_ORDER, + CLAY_EXIT_TRANSITION_ORDERING_ABOVE_SIBLINGS, + }; + } exit; +} Clay_TransitionElementConfig; +``` + +**Fields** + +**`.handler`** - `bool (Clay_TransitionCallbackArguments arguments) {}` + +`CLAY(CLAY_ID("Transition"), { .transition = { .handler = Clay_EaseOut } })` + +When a transition has begun, this function will be called each frame to determine the current state of the element in transition. Clay provides the built-in `Clay_EaseOut` function which uses a standard [EaseOut](https://easings.net/) curve. + +If you want to implement your own transition handler, the handler function takes [Clay_TransitionCallbackArguments](todo) and returns a `bool` to indicate whether the transition has finished or not (`return true` means the transition is complete, `return false` means that the handler should be called again next frame) +Consider inspecting the source of the [Clay_EaseOut]() function for more information. + +```C +// Example custom handler +bool TransitionHandler(Clay_TransitionCallbackArguments arguments) { + float ratio = 1; + if (arguments.duration > 0) { + // You may want to guard against durations of zero if you use them + ratio = arguments.elapsedTime / arguments.duration; + } + float lerpAmount = (1 - powf(1 - CLAY__MIN(ratio, 1.f), 3.0f)); + // Only animate properties that were specified in the original config + if (arguments.properties & CLAY_TRANSITION_PROPERTY_X) { + // Clay provides the initial state from when the transition first started, as well as the target state, to allow + // easy interpolation + arguments.current->boundingBox.x = Lerp(arguments.initial.boundingBox.x, arguments.target.boundingBox.x, lerpAmount); + } + if (arguments.properties & CLAY_TRANSITION_PROPERTY_Y) { + arguments.current->boundingBox.y = Lerp(arguments.initial.boundingBox.y, arguments.target.boundingBox.y, lerpAmount); + } + // etc... + + // End (return true) once elapsedTime is greater than duration + return ratio >= 1; +} +``` + +--- + +**`.duration`** - `float` + +`CLAY(CLAY_ID("Transition"), { .transition = { .duration = 0.2f } })` + +The duration in seconds that the transition should take to arrive at its target state. Passed through to the handler function. + +--- + +**`.properties`** - `Clay_TransitionProperty` + +`CLAY(CLAY_ID("Transition"), { .transition = { .properties = CLAY_TRANSITION_PROPERTY_X | CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR } })` + +A flag field containing the properties on which to transition. Any properties that change but are not listed here will immediately snap to their target value. + +This flag is a bitfield, which means that you will use bitwise operations to interact with it. For example, use bitwise OR `|` to combine multiple flags, or bitwise AND `&` to test if a flag is switched on. + +The full list of available transition properties is as follows: + +```C +typedef enum { + CLAY_TRANSITION_PROPERTY_NONE (default), + CLAY_TRANSITION_PROPERTY_X, + CLAY_TRANSITION_PROPERTY_Y, + CLAY_TRANSITION_PROPERTY_POSITION = CLAY_TRANSITION_PROPERTY_X | CLAY_TRANSITION_PROPERTY_Y, + CLAY_TRANSITION_PROPERTY_WIDTH, + CLAY_TRANSITION_PROPERTY_HEIGHT, + CLAY_TRANSITION_PROPERTY_DIMENSIONS = CLAY_TRANSITION_PROPERTY_WIDTH | CLAY_TRANSITION_PROPERTY_HEIGHT, + CLAY_TRANSITION_PROPERTY_BOUNDING_BOX = CLAY_TRANSITION_PROPERTY_POSITION | CLAY_TRANSITION_PROPERTY_DIMENSIONS, + CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR, + CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR, + CLAY_TRANSITION_PROPERTY_CORNER_RADIUS, + CLAY_TRANSITION_PROPERTY_BORDER_COLOR, + CLAY_TRANSITION_PROPERTY_BORDER_WIDTH, + CLAY_TRANSITION_PROPERTY_BORDER = CLAY_TRANSITION_PROPERTY_BORDER_COLOR | CLAY_TRANSITION_PROPERTY_BORDER_WIDTH +} Clay_TransitionProperty; +``` + +--- + +**`.interactionHandling`** - `Clay_TransitionInteractionHandlingType` + +`CLAY(CLAY_ID("Transition"), { .transition = { .interactionHandling = CLAY_TRANSITION_ALLOW_INTERACTIONS_WHILE_TRANSITIONING_POSITION } })` + +This flag controls how interactions are handled when elements are transitioning their **positions**. By default, Clay will ignore interactions i.e. returning `false` for functions like `Clay_Hovered()` when `.interactionHandling` is in the default mode of `CLAY_TRANSITION_DISABLE_INTERACTIONS_WHILE_TRANSITIONING` + +You can set `.interactionHandling = CLAY_TRANSITION_ALLOW_INTERACTIONS_WHILE_TRANSITIONING_POSITION` if you want to interact with transitioning elements. + +The full list of values is as follows: + +```C +typedef enum { + CLAY_TRANSITION_DISABLE_INTERACTIONS_WHILE_TRANSITIONING_POSITION (default), + CLAY_TRANSITION_ALLOW_INTERACTIONS_WHILE_TRANSITIONING_POSITION, +} Clay_TransitionInteractionHandlingType; +``` + +--- + +**`.enter.setInitialState`** - `Clay_TransitionData (Clay_TransitionData targetState, Clay_TransitionProperty properties) {}` + +`CLAY(CLAY_ID("Transition"), { .transition = { .enter = { .setInitialState = { EnterSlideUp } } })` + +This function pointer is called the first frame an element appears, and allows you to modify the "initial state" of the element to create an entry transition. Common techniques include offsetting the y position to "slide up / down", +or using a `.overlayColor` matched to the background color to fade in. + +The function will be called with `Clay_TransitionData` that provides the first-frame state of the element, which you can modify and then return. + +**Note: "Enter" transitions will only trigger if this function pointer is set.** + +Here is an example "fade in & slide up" function: + +```C +Clay_TransitionData EnterSlideUp(Clay_TransitionData initialState, Clay_TransitionProperty properties) { + Clay_TransitionData targetState = initialState; + if (properties & CLAY_TRANSITION_PROPERTY_Y) { + targetState.boundingBox.y += 20; + } + if (properties & CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR) { + // Assuming the background color is white, this will produce an alpha-like "fade-in" effect + targetState.overlayColor = (Clay_Color) { 255, 255, 255, 255 }; + } + return targetState; +} +``` + +--- + +**`.enter.trigger`** - `Clay_TransitionEnterTriggerType` + +`CLAY(CLAY_ID("Transition"), { .transition = { .enter = { .trigger = CLAY_TRANSITION_ENTER_TRIGGER_ON_FIRST_PARENT_FRAME } } })` + +This flag controls whether or not the "enter" transition of this element will trigger on the same frame that the parent element first appeared. + +A common use case for enter transitions is adding items to animated lists. Without this flag, the first frame the list is displayed, all the item enter animations will simultaneously trigger, which is usually undesirable. + +For cases where you _do_ want enter animations to trigger when the parent first appears, you can set `.trigger = CLAY_TRANSITION_ENTER_TRIGGER_ON_FIRST_PARENT_FRAME`. + +The full list of values is as follows: + +```C +typedef enum trigger { + CLAY_TRANSITION_ENTER_SKIP_ON_FIRST_PARENT_FRAME (default), + CLAY_TRANSITION_ENTER_TRIGGER_ON_FIRST_PARENT_FRAME, +} Clay_TransitionEnterTriggerType; +``` + +--- + +**`.exit.setFinalState`** - `Clay_TransitionData (Clay_TransitionData currentState, Clay_TransitionProperty properties) {}` + +`CLAY(CLAY_ID("Transition"), { .transition = { .exit = { .setFinalState = { ExitSlideDown } } })` + +This function pointer is called the frame the element "exits" (i.e. it was in the layout tree last frame, and this frame it isn't). +It allows you to modify the "final state" of the element to create an exit transition. Common techniques include offsetting the y position to "slide up / down", +or using a `.overlayColor` matched to the background color to fade out. + +The function will be called with `Clay_TransitionData` that provides the final-frame state of the element, which you can modify and then return. + +**Note: "Exit" transitions will only trigger if this function pointer is set.** + +Here is an example "fade out & slide down" function: + +```C +Clay_TransitionData ExitSlideDown(Clay_TransitionData currentState, Clay_TransitionProperty properties) { + Clay_TransitionData finalState = currentState; + if (properties & CLAY_TRANSITION_PROPERTY_Y) { + finalState.boundingBox.y += 20; + } + if (properties & CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR) { + // Assuming the background color is white, this will produce an alpha-like "fade-out" effect + finalState.overlayColor = (Clay_Color) { 255, 255, 255, 255 }; + } + return finalState; +} +``` + +--- + +**`.exit.trigger`** - `Clay_TransitionExitTriggerType` + +`CLAY(CLAY_ID("Transition"), { .transition = { .exit = { .trigger = CLAY_TRANSITION_EXIT_TRIGGER_WHEN_PARENT_EXITS } } })` + +This flag controls whether or not the "exit" transition of this element will trigger on the same frame that the parent element disappears. + +A common use case for exit transitions is removing items from animated lists. Without this flag, the frame after the list disappears (due to say, menu navigation), all the item exit animations will simultaneously trigger, which is usually undesirable. + +For cases where you _do_ want exit animations to trigger when the parent exits, you can set `.trigger = CLAY_TRANSITION_EXIT_TRIGGER_WHEN_PARENT_EXITS`. + +The full list of values is as follows: + +```C +typedef enum { + CLAY_TRANSITION_EXIT_SKIP_WHEN_PARENT_EXITS (default), + CLAY_TRANSITION_EXIT_TRIGGER_WHEN_PARENT_EXITS, +} Clay_TransitionExitTriggerType; +``` + +--- + +**`.exit.siblingOrdering`** - `Clay_ExitTransitionSiblingOrdering` + +`CLAY(CLAY_ID("Transition"), { .transition = { .exit = { .siblingOrdering = CLAY_EXIT_TRANSITION_ORDERING_NATURAL_ORDER } } })` + +This flag controls the relative z-ordering of exiting elements. + +It's often useful to be able to control whether you want an exiting element to appear "underneath siblings" in its "natural order" (i.e. above the previous sibling, below the subsequent sibling) or "above all siblings." + +By default, exiting elements will be underneath siblings as this appears to be the most common case. + +The full list of values is as follows: + +```C +typedef enum { + CLAY_EXIT_TRANSITION_ORDERING_UNDERNEATH_SIBLINGS (default), + CLAY_EXIT_TRANSITION_ORDERING_NATURAL_ORDER, + CLAY_EXIT_TRANSITION_ORDERING_ABOVE_SIBLINGS, +} Clay_ExitTransitionSiblingOrdering; +``` + +--- + +**Examples** + +See video below for how the following would look. + +```C +// Note: for transitions to work, elements need a stable ID from one frame to the next - using loop indexes or CLAY_AUTO_ID will not work. +CLAY(CLAY_IDI("box", colors[index].id), { + .layout.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, + .layout.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }, + .backgroundColor = boxColor, + .overlayColor = Clay_Hovered() ? (Clay_Color) { 140, 140, 140, 80 } : (Clay_Color) { 255, 255, 255, 0 }, + // Transitions will activate once a handler function is defined. + .transition = { + .handler = Clay_EaseOut, + .duration = 0.5f, + // A "flag" enum is used to specify which properties to transition, use a bitwise OR (|) to construct the flags. + .properties = CLAY_TRANSITION_PROPERTY_WIDTH + | CLAY_TRANSITION_PROPERTY_POSITION + | CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR + | CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR, + .enter = { .setInitialState = EnterExitSlideUp }, + .exit = { .setFinalState = EnterExitSlideUp }, + } +}) { + CLAY_TEXT(CLAY_STRING("Animated Box"), { + .fontSize = 32, + .textColor = colors[index].id > 29 ? (Clay_Color) { 255, 255, 255, 255 } : (Clay_Color) { 154, 123, 184, 255 } + }); +} +``` + + + +_An example of the transition API action can be found at examples/raylib-transitions_ + ### Clay_Color ```C @@ -1989,8 +2444,8 @@ Stores the original string that was passed in when [CLAY_ID](#clay_id) or [CLAY_ ```C typedef struct { - uint32_t capacity; - uint32_t length; + int32_t capacity; + int32_t length; Clay_RenderCommand *internalArray; } Clay_RenderCommandArray; ``` @@ -2023,7 +2478,7 @@ An array of [Clay_RenderCommand](#clay_rendercommand)s representing the calculat typedef struct { Clay_BoundingBox boundingBox; Clay_RenderData renderData; - uintptr_t userData; + void* userData; uint32_t id; int16_t zIndex; Clay_RenderCommandType commandType; @@ -2037,13 +2492,15 @@ typedef struct { An enum indicating how this render command should be handled. Possible values include: - `CLAY_RENDER_COMMAND_TYPE_NONE` - Should be ignored by the renderer, and never emitted by clay under normal conditions. -- `CLAY_RENDER_COMMAND_TYPE_RECTANGLE` - A rectangle should be drawn, configured with `.config.rectangleElementConfig` -- `CLAY_RENDER_COMMAND_TYPE_BORDER` - A border should be drawn, configured with `.config.borderElementConfig` -- `CLAY_RENDER_COMMAND_TYPE_TEXT` - Text should be drawn, configured with `.config.textElementConfig` -- `CLAY_RENDER_COMMAND_TYPE_IMAGE` - An image should be drawn, configured with `.config.imageElementConfig` +- `CLAY_RENDER_COMMAND_TYPE_RECTANGLE` - A rectangle should be drawn, configured with `.renderData.rectangle` +- `CLAY_RENDER_COMMAND_TYPE_BORDER` - A border should be drawn, configured with `.renderData.border` +- `CLAY_RENDER_COMMAND_TYPE_TEXT` - Text should be drawn, configured with `.renderData.text` +- `CLAY_RENDER_COMMAND_TYPE_IMAGE` - An image should be drawn, configured with `.renderData.image` - `CLAY_RENDER_COMMAND_TYPE_SCISSOR_START` - Named after [glScissor](https://registry.khronos.org/OpenGL-Refpages/gl4/html/glScissor.xhtml), this indicates that the renderer should begin culling any subsequent pixels that are drawn outside the `.boundingBox` of this render command. - `CLAY_RENDER_COMMAND_TYPE_SCISSOR_END` - Only ever appears after a matching `CLAY_RENDER_COMMAND_TYPE_SCISSOR_START` command, and indicates that the scissor has ended. -- `CLAY_RENDER_COMMAND_TYPE_CUSTOM` - A custom render command controlled by the user, configured with `.config.customElementConfig` +- `CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START` - The renderer should begin applying an overlay color to all subsequent render commands, similar to glsl's `mix(source, target, alpha)`. +- `CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_END` - The previous `OVERLAY_COLOR` should be removed. Note: nested color overlays may require a stack data structure on the renderer side. +- `CLAY_RENDER_COMMAND_TYPE_CUSTOM` - A custom render command controlled by the user, configured with `.renderData.custom` --- @@ -2086,11 +2543,13 @@ typedef union { A C union containing various structs, with the type dependent on `.commandType`. Possible values include: -- `config.rectangle` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE`. -- `config.text` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_TEXT`. See [Clay_Text](#clay_text) for details. -- `config.image` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE`. See [Clay_Image](#clay_imageelementconfig) for details. -- `config.border` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_BORDER`. See [Clay_Border](#clay_borderelementconfig) for details. -- `config.custom` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM`. See [Clay_Custom](#clay_customelementconfig) for details. +- `renderData.rectangle` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE`. +- `renderData.text` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_TEXT`. See [Clay_Text](#clay_text) for details. +- `renderData.image` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE`. See [Clay_ImageElementConfig](#clay_imageelementconfig) for details. +- `renderData.custom` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM`. See [Clay_CustomElementConfig](#clay_customelementconfig) for details. +- `renderData.border` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_BORDER`. See [Clay_BorderElementConfig](#clay_borderelementconfig) for details. +- `renderData.clip` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START`. See [Clay_ClipElementConfig](#clay_clipelementconfig) for details. +- `renderData.overlayColor` - Used when `.commandType == CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START`. See [Clay_ElementDeclaration.overlayColor](#clay_elementdeclaration) for details. **Union Structs** @@ -2331,7 +2790,7 @@ An enum representing the type of error Clay encountered. It's up to the user to - `CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED` - The user is attempting to use `CLAY_TEXT` and either forgot to call [Clay_SetMeasureTextFunction](#clay_setmeasuretextfunction) or accidentally passed a null function pointer. - `CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED` - Clay was initialized with an Arena that was too small for the configured [Clay_SetMaxElementCount](#clay_setmaxelementcount). Try using [Clay_MinMemorySize()](#clay_minmemorysize) to get the exact number of bytes required by the current configuration. - `CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED` - The declared UI hierarchy has too many elements for the configured max element count. Use [Clay_SetMaxElementCount](#clay_setmaxelementcount) to increase the max, then call [Clay_MinMemorySize()](#clay_minmemorysize) again and reinitialize clay's memory with the required size. -- `CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED` - The declared UI hierarchy has too much text for the configured text measure cache size. Use [Clay_SetMaxMeasureTextCacheWordCount](#clay_setmeasuretextcachesize) to increase the max, then call [Clay_MinMemorySize()](#clay_minmemorysize) again and reinitialize clay's memory with the required size. +- `CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED` - The declared UI hierarchy has too much text for the configured text measure cache size. Use [Clay_SetMaxMeasureTextCacheWordCount](#clay_setmeasuretextcachesize) to increase the max, then call [Clay_MinMemorySize()](#clay_minmemorysize) again and reinitialize clay's memory with the required size. - `CLAY_ERROR_TYPE_DUPLICATE_ID` - Two elements in Clays UI Hierarchy have been declared with exactly the same ID. Set a breakpoint in your error handler function for a stack trace back to exactly where this occured. - `CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND` - A `CLAY_FLOATING` element was declared with the `.parentId` property, but no element with that ID was found. Set a breakpoint in your error handler function for a stack trace back to exactly where this occured. - `CLAY_ERROR_TYPE_INTERNAL_ERROR` - Clay has encountered an internal logic or memory error. Please report this as a bug with a stack trace to help us fix these! diff --git a/clay.h b/clay.h index 674e845..ec9dd52 100644 --- a/clay.h +++ b/clay.h @@ -20,6 +20,9 @@ #elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) #include #endif +#if __CLION_IDE__ +#define CLAY_IMPLEMENTATION +#endif // ----------------------------------------- // HEADER DECLARATIONS --------------------- @@ -54,7 +57,7 @@ #define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y)) #define CLAY__MIN(x, y) (((x) < (y)) ? (x) : (y)) -#define CLAY_TEXT_CONFIG(...) Clay__StoreTextElementConfig(CLAY__CONFIG_WRAPPER(Clay_TextElementConfig, __VA_ARGS__)) +#define CLAY_TEXT_CONFIG(...) __VA_ARGS__ #define CLAY_BORDER_OUTSIDE(widthValue) {widthValue, widthValue, widthValue, widthValue, 0} @@ -85,12 +88,12 @@ // Note: If a compile error led you here, you might be trying to use CLAY_ID_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID_LOCAL instead. #define CLAY_ID_LOCAL(label) CLAY_SID_LOCAL(CLAY_STRING(label)) -#define CLAY_SID_LOCAL(label) Clay__HashString(label, Clay__GetParentElementId()) +#define CLAY_SID_LOCAL(label) Clay__HashString(label, Clay_GetOpenElementId()) // Note: If a compile error led you here, you might be trying to use CLAY_IDI_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI_LOCAL instead. #define CLAY_IDI_LOCAL(label, index) CLAY_SIDI_LOCAL(CLAY_STRING(label), index) -#define CLAY_SIDI_LOCAL(label, index) Clay__HashStringWithOffset(label, index, Clay__GetParentElementId()) +#define CLAY_SIDI_LOCAL(label, index) Clay__HashStringWithOffset(label, index, Clay_GetOpenElementId()) #define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0])) @@ -156,7 +159,7 @@ static inline void Clay__SuppressUnusedLatchDefinitionVariableWarning(void) { (v #define CLAY__WRAPPER_STRUCT(type) typedef struct { type wrapped; } CLAY__WRAPPER_TYPE(type) #define CLAY__CONFIG_WRAPPER(type, ...) (CLAY__INIT(CLAY__WRAPPER_TYPE(type)) { __VA_ARGS__ }).wrapped -#define CLAY_TEXT(text, textConfig) Clay__OpenTextElement(text, textConfig) +#define CLAY_TEXT(text, ...) Clay__OpenTextElement(text, CLAY__CONFIG_WRAPPER(Clay_TextElementConfig, __VA_ARGS__)) #ifdef __cplusplus @@ -452,7 +455,6 @@ typedef CLAY_PACKED_ENUM { // (default) "Capture" the pointer event and don't allow events like hover and click to pass through to elements underneath. CLAY_POINTER_CAPTURE_MODE_CAPTURE, // CLAY_POINTER_CAPTURE_MODE_PARENT, TODO pass pointer through to attached parent - // Transparently pass through pointer events like hover and click to elements underneath the floating element. CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, } Clay_PointerCaptureMode; @@ -557,6 +559,88 @@ typedef struct Clay_BorderElementConfig { CLAY__WRAPPER_STRUCT(Clay_BorderElementConfig); +typedef struct { + Clay_BoundingBox boundingBox; + Clay_Color backgroundColor; + Clay_Color overlayColor; + Clay_Color borderColor; + Clay_BorderWidth borderWidth; +} Clay_TransitionData; + +typedef enum { + CLAY_TRANSITION_STATE_IDLE, + CLAY_TRANSITION_STATE_ENTERING, + CLAY_TRANSITION_STATE_TRANSITIONING, + CLAY_TRANSITION_STATE_EXITING, +} Clay_TransitionState; + +typedef enum { + CLAY_TRANSITION_PROPERTY_NONE = 0, + CLAY_TRANSITION_PROPERTY_X = 1, + CLAY_TRANSITION_PROPERTY_Y = 2, + CLAY_TRANSITION_PROPERTY_POSITION = CLAY_TRANSITION_PROPERTY_X | CLAY_TRANSITION_PROPERTY_Y, + CLAY_TRANSITION_PROPERTY_WIDTH = 4, + CLAY_TRANSITION_PROPERTY_HEIGHT = 8, + CLAY_TRANSITION_PROPERTY_DIMENSIONS = CLAY_TRANSITION_PROPERTY_WIDTH | CLAY_TRANSITION_PROPERTY_HEIGHT, + CLAY_TRANSITION_PROPERTY_BOUNDING_BOX = CLAY_TRANSITION_PROPERTY_POSITION | CLAY_TRANSITION_PROPERTY_DIMENSIONS, + CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR = 16, + CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR = 32, + CLAY_TRANSITION_PROPERTY_CORNER_RADIUS = 64, + CLAY_TRANSITION_PROPERTY_BORDER_COLOR = 128, + CLAY_TRANSITION_PROPERTY_BORDER_WIDTH = 256, + CLAY_TRANSITION_PROPERTY_BORDER = CLAY_TRANSITION_PROPERTY_BORDER_COLOR | CLAY_TRANSITION_PROPERTY_BORDER_WIDTH +} Clay_TransitionProperty; + +typedef struct { + Clay_TransitionState transitionState; + Clay_TransitionData initial; + Clay_TransitionData *current; + Clay_TransitionData target; + float elapsedTime; + float duration; + Clay_TransitionProperty properties; +} Clay_TransitionCallbackArguments; + +typedef CLAY_PACKED_ENUM { + CLAY_TRANSITION_ENTER_SKIP_ON_FIRST_PARENT_FRAME, + CLAY_TRANSITION_ENTER_TRIGGER_ON_FIRST_PARENT_FRAME, +} Clay_TransitionEnterTriggerType; + +typedef CLAY_PACKED_ENUM { + CLAY_TRANSITION_EXIT_SKIP_WHEN_PARENT_EXITS, + CLAY_TRANSITION_EXIT_TRIGGER_WHEN_PARENT_EXITS, +} Clay_TransitionExitTriggerType; + +typedef CLAY_PACKED_ENUM { + CLAY_TRANSITION_DISABLE_INTERACTIONS_WHILE_TRANSITIONING_POSITION, + CLAY_TRANSITION_ALLOW_INTERACTIONS_WHILE_TRANSITIONING_POSITION, +} Clay_TransitionInteractionHandlingType; + +typedef CLAY_PACKED_ENUM { + CLAY_EXIT_TRANSITION_ORDERING_UNDERNEATH_SIBLINGS, + CLAY_EXIT_TRANSITION_ORDERING_NATURAL_ORDER, + CLAY_EXIT_TRANSITION_ORDERING_ABOVE_SIBLINGS, +} Clay_ExitTransitionSiblingOrdering; + +// Controls settings related to transitions +typedef struct Clay_TransitionElementConfig { + bool (*handler)(Clay_TransitionCallbackArguments arguments); + float duration; + Clay_TransitionProperty properties; + Clay_TransitionInteractionHandlingType interactionHandling; + struct { + Clay_TransitionData (*setInitialState)(Clay_TransitionData targetState, Clay_TransitionProperty properties); + Clay_TransitionEnterTriggerType trigger; + } enter; + struct { + Clay_TransitionData (*setFinalState)(Clay_TransitionData initialState, Clay_TransitionProperty properties); + Clay_TransitionExitTriggerType trigger; + Clay_ExitTransitionSiblingOrdering siblingOrdering; + } exit; +} Clay_TransitionElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_TransitionElementConfig); + // Render Command Data ----------------------------- // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_TEXT @@ -610,11 +694,16 @@ typedef struct Clay_CustomRenderData { } Clay_CustomRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START || commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_END -typedef struct Clay_ScrollRenderData { +typedef struct Clay_ClipRenderData { bool horizontal; bool vertical; } Clay_ClipRenderData; +// Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START || commandType == CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_END +typedef struct Clay_OverlayColorRenderData { + Clay_Color color; +} Clay_OverlayColorRenderData; + // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_BORDER typedef struct Clay_BorderRenderData { // Controls a shared color for all this element's borders. @@ -641,6 +730,8 @@ typedef union Clay_RenderData { Clay_BorderRenderData border; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START|END Clay_ClipRenderData clip; + // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START|END + Clay_OverlayColorRenderData overlayColor; } Clay_RenderData; // Miscellaneous Structs & Enums --------------------------------- @@ -684,6 +775,10 @@ typedef CLAY_PACKED_ENUM { CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, // The renderer should finish any previously active clipping, and begin rendering elements in full again. CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, + // The renderer should begin performing a "color overlay" on all subsequent render commands until disabled again. + CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START, + // The renderer should disable any previously active "color overlay" and render elements with their standard colors again. + CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_END, // The renderer should provide a custom implementation for handling this render command based on its .customData CLAY_RENDER_COMMAND_TYPE_CUSTOM, } Clay_RenderCommandType; @@ -753,6 +848,9 @@ typedef struct Clay_ElementDeclaration { // By convention specified as 0-255, but interpretation is up to the renderer. // If no other config is specified, .backgroundColor will generate a RECTANGLE render command, otherwise it will be passed as a property to IMAGE or CUSTOM render commands. Clay_Color backgroundColor; + // Perform an image editing style "Color Overlay" on this element and all its children, equivalent to + // glsl mix(elementColor, overlayColor.rgb, overlayColor.a) + Clay_Color overlayColor; // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. Clay_CornerRadius cornerRadius; // Controls settings related to aspect ratio scaling. @@ -768,6 +866,7 @@ typedef struct Clay_ElementDeclaration { Clay_ClipElementConfig clip; // Controls settings related to element borders, and will generate BORDER render commands. Clay_BorderElementConfig border; + Clay_TransitionElementConfig transition; // A pointer that will be transparently passed through to resulting render commands. void *userData; } Clay_ElementDeclaration; @@ -837,6 +936,8 @@ CLAY_DLL_EXPORT Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity // Sets the state of the "pointer" (i.e. the mouse or touch) in Clay's internal data. Used for detecting and responding to mouse events in the debug view, // as well as for Clay_Hovered() and scroll element handling. CLAY_DLL_EXPORT void Clay_SetPointerState(Clay_Vector2 position, bool pointerDown); +// Returns the state of the "pointer" (i.e. the mouse or touch) which was set via Clay_SetPointerState(). +CLAY_DLL_EXPORT Clay_PointerData Clay_GetPointerState(void); // Initialize Clay's internal arena and setup required data before layout can begin. Only needs to be called once. // - arena can be created using Clay_CreateArenaWithCapacityAndMemory() // - layoutDimensions are the initial bounding dimensions of the layout (i.e. the screen width and height for a full screen layout) @@ -861,7 +962,9 @@ CLAY_DLL_EXPORT void Clay_SetLayoutDimensions(Clay_Dimensions dimensions); CLAY_DLL_EXPORT void Clay_BeginLayout(void); // Called when all layout declarations are finished. // Computes the layout and generates and returns the array of render commands to draw. -CLAY_DLL_EXPORT Clay_RenderCommandArray Clay_EndLayout(void); +CLAY_DLL_EXPORT Clay_RenderCommandArray Clay_EndLayout(float deltaTime); +// Gets the ID of the currently open element, useful for retrieving IDs generated by CLAY_AUTO_ID() +CLAY_DLL_EXPORT uint32_t Clay_GetOpenElementId(void); // Calculates a hash ID from the given idString. // Generally only used for dynamic strings when CLAY_ID("stringLiteral") can't be used. CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementId(Clay_String idString); @@ -928,9 +1031,7 @@ CLAY_DLL_EXPORT void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration CLAY_DLL_EXPORT void Clay__CloseElement(void); CLAY_DLL_EXPORT Clay_ElementId Clay__HashString(Clay_String key, uint32_t seed); CLAY_DLL_EXPORT Clay_ElementId Clay__HashStringWithOffset(Clay_String key, uint32_t offset, uint32_t seed); -CLAY_DLL_EXPORT void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig); -CLAY_DLL_EXPORT Clay_TextElementConfig *Clay__StoreTextElementConfig(Clay_TextElementConfig config); -CLAY_DLL_EXPORT uint32_t Clay__GetParentElementId(void); +CLAY_DLL_EXPORT void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig textConfig); extern Clay_Color Clay__debugViewHighlightColor; extern uint32_t Clay__debugViewWidth; @@ -985,6 +1086,10 @@ typeName arrayName##_GetValue(arrayName *array, int32_t index) { return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : typeName##_DEFAULT; \ } \ \ +typeName *arrayName##_GetCheckCapacity(arrayName *array, int32_t index) { \ + return Clay__Array_RangeCheck(index, array->capacity) ? &array->internalArray[index] : &typeName##_DEFAULT; \ +} \ + \ typeName *arrayName##_Add(arrayName *array, typeName item) { \ if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { \ array->internalArray[array->length++] = item; \ @@ -1007,12 +1112,22 @@ typeName arrayName##_RemoveSwapback(arrayName *array, int32_t index) { return typeName##_DEFAULT; \ } \ \ -void arrayName##_Set(arrayName *array, int32_t index, typeName value) { \ +typeName* arrayName##_Set(arrayName *array, int32_t index, typeName value) { \ if (Clay__Array_RangeCheck(index, array->capacity)) { \ array->internalArray[index] = value; \ array->length = index < array->length ? array->length : index + 1; \ + return &array->internalArray[index];\ } \ + return NULL;\ } \ + \ +typeName* arrayName##_Set_DontTouchLength(arrayName *array, int32_t index, typeName value) { \ + if (Clay__Array_RangeCheck(index, array->capacity)) { \ + array->internalArray[index] = value; \ + return &array->internalArray[index];\ + } \ + return NULL;\ +} \ #define CLAY__ARRAY_DEFINE(typeName, arrayName) \ typedef struct \ @@ -1055,14 +1170,6 @@ typedef struct { Clay__Warning *internalArray; } Clay__WarningArray; -typedef struct { - Clay_Color backgroundColor; - Clay_CornerRadius cornerRadius; - void* userData; -} Clay_SharedElementConfig; - -CLAY__WRAPPER_STRUCT(Clay_SharedElementConfig); - Clay__WarningArray Clay__WarningArray_Allocate_Arena(int32_t capacity, Clay_Arena *arena); Clay__Warning *Clay__WarningArray_Add(Clay__WarningArray *array, Clay__Warning item); void* Clay__Array_Allocate_Arena(int32_t capacity, uint32_t itemSize, Clay_Arena *arena); @@ -1073,48 +1180,9 @@ CLAY__ARRAY_DEFINE(bool, Clay__boolArray) CLAY__ARRAY_DEFINE(int32_t, Clay__int32_tArray) CLAY__ARRAY_DEFINE(char, Clay__charArray) CLAY__ARRAY_DEFINE_FUNCTIONS(Clay_ElementId, Clay_ElementIdArray) -CLAY__ARRAY_DEFINE(Clay_LayoutConfig, Clay__LayoutConfigArray) -CLAY__ARRAY_DEFINE(Clay_TextElementConfig, Clay__TextElementConfigArray) -CLAY__ARRAY_DEFINE(Clay_AspectRatioElementConfig, Clay__AspectRatioElementConfigArray) -CLAY__ARRAY_DEFINE(Clay_ImageElementConfig, Clay__ImageElementConfigArray) -CLAY__ARRAY_DEFINE(Clay_FloatingElementConfig, Clay__FloatingElementConfigArray) -CLAY__ARRAY_DEFINE(Clay_CustomElementConfig, Clay__CustomElementConfigArray) -CLAY__ARRAY_DEFINE(Clay_ClipElementConfig, Clay__ClipElementConfigArray) -CLAY__ARRAY_DEFINE(Clay_BorderElementConfig, Clay__BorderElementConfigArray) CLAY__ARRAY_DEFINE(Clay_String, Clay__StringArray) -CLAY__ARRAY_DEFINE(Clay_SharedElementConfig, Clay__SharedElementConfigArray) CLAY__ARRAY_DEFINE_FUNCTIONS(Clay_RenderCommand, Clay_RenderCommandArray) -typedef CLAY_PACKED_ENUM { - CLAY__ELEMENT_CONFIG_TYPE_NONE, - CLAY__ELEMENT_CONFIG_TYPE_BORDER, - CLAY__ELEMENT_CONFIG_TYPE_FLOATING, - CLAY__ELEMENT_CONFIG_TYPE_CLIP, - CLAY__ELEMENT_CONFIG_TYPE_ASPECT, - CLAY__ELEMENT_CONFIG_TYPE_IMAGE, - CLAY__ELEMENT_CONFIG_TYPE_TEXT, - CLAY__ELEMENT_CONFIG_TYPE_CUSTOM, - CLAY__ELEMENT_CONFIG_TYPE_SHARED, -} Clay__ElementConfigType; - -typedef union { - Clay_TextElementConfig *textElementConfig; - Clay_AspectRatioElementConfig *aspectRatioElementConfig; - Clay_ImageElementConfig *imageElementConfig; - Clay_FloatingElementConfig *floatingElementConfig; - Clay_CustomElementConfig *customElementConfig; - Clay_ClipElementConfig *clipElementConfig; - Clay_BorderElementConfig *borderElementConfig; - Clay_SharedElementConfig *sharedElementConfig; -} Clay_ElementConfigUnion; - -typedef struct { - Clay__ElementConfigType type; - Clay_ElementConfigUnion config; -} Clay_ElementConfig; - -CLAY__ARRAY_DEFINE(Clay_ElementConfig, Clay__ElementConfigArray) - typedef struct { Clay_Dimensions dimensions; Clay_String line; @@ -1125,28 +1193,31 @@ CLAY__ARRAY_DEFINE(Clay__WrappedTextLine, Clay__WrappedTextLineArray) typedef struct { Clay_String text; Clay_Dimensions preferredDimensions; - int32_t elementIndex; Clay__WrappedTextLineArraySlice wrappedLines; } Clay__TextElementData; -CLAY__ARRAY_DEFINE(Clay__TextElementData, Clay__TextElementDataArray) - typedef struct { int32_t *elements; uint16_t length; } Clay__LayoutElementChildren; -typedef struct { - union { - Clay__LayoutElementChildren children; - Clay__TextElementData *textElementData; - } childrenOrTextContent; +typedef struct Clay_LayoutElement { + Clay__LayoutElementChildren children; Clay_Dimensions dimensions; Clay_Dimensions minDimensions; - Clay_LayoutConfig *layoutConfig; - Clay__ElementConfigArraySlice elementConfigs; + union { + Clay_ElementDeclaration config; + struct { + Clay_TextElementConfig textConfig; + Clay__TextElementData textElementData; + }; + }; uint32_t id; uint16_t floatingChildrenCount; + bool isTextElement; + // True if the element is currently in an exit transition, and is "synthetic" + // i.e. data was retained from previous frames + bool exiting; } Clay_LayoutElement; CLAY__ARRAY_DEFINE(Clay_LayoutElement, Clay_LayoutElementArray) @@ -1168,6 +1239,24 @@ typedef struct { CLAY__ARRAY_DEFINE(Clay__ScrollContainerDataInternal, Clay__ScrollContainerDataInternalArray) +// Data representing the current internal state of a transition element. +typedef struct Clay__TransitionDataInternal { + Clay_TransitionData initialState; + Clay_TransitionData currentState; + Clay_TransitionData targetState; + Clay_LayoutElement* elementThisFrame; + uint32_t elementId; + uint32_t parentId; + uint32_t siblingIndex; + float elapsedTime; + Clay_TransitionState state; + bool transitionOut; + bool reparented; + Clay_TransitionProperty activeProperties; +} Clay__TransitionDataInternal; + +CLAY__ARRAY_DEFINE(Clay__TransitionDataInternal, Clay__TransitionDataInternalArray) + typedef struct { bool collision; bool collapsed; @@ -1183,6 +1272,7 @@ typedef struct { // todo get this struct into a single cache line void *hoverFunctionUserData; int32_t nextIndex; uint32_t generation; + bool appearedThisFrame; Clay__DebugElementData *debugData; } Clay_LayoutElementHashMapItem; @@ -1214,6 +1304,7 @@ typedef struct { Clay_LayoutElement *layoutElement; Clay_Vector2 position; Clay_Vector2 nextChildOffset; + bool parentMovedThisFramed; // Used to relativise transitions } Clay__LayoutElementTreeNode; CLAY__ARRAY_DEFINE(Clay__LayoutElementTreeNode, Clay__LayoutElementTreeNodeArray) @@ -1231,7 +1322,10 @@ CLAY__ARRAY_DEFINE(Clay__LayoutElementTreeRoot, Clay__LayoutElementTreeRootArray struct Clay_Context { int32_t maxElementCount; int32_t maxMeasureTextCacheWordCount; + int32_t exitingElementsLength; + int32_t exitingElementsChildrenLength; bool warningsEnabled; + bool rootResizedLastFrame; Clay_ErrorHandler errorHandler; Clay_BooleanWarnings booleanWarnings; Clay__WarningArray warnings; @@ -1255,21 +1349,8 @@ struct Clay_Context { Clay__int32_tArray openLayoutElementStack; Clay__int32_tArray layoutElementChildren; Clay__int32_tArray layoutElementChildrenBuffer; - Clay__TextElementDataArray textElementData; - Clay__int32_tArray aspectRatioElementIndexes; Clay__int32_tArray reusableElementIndexBuffer; Clay__int32_tArray layoutElementClipElementIds; - // Configs - Clay__LayoutConfigArray layoutConfigs; - Clay__ElementConfigArray elementConfigs; - Clay__TextElementConfigArray textElementConfigs; - Clay__AspectRatioElementConfigArray aspectRatioElementConfigs; - Clay__ImageElementConfigArray imageElementConfigs; - Clay__FloatingElementConfigArray floatingElementConfigs; - Clay__ClipElementConfigArray clipElementConfigs; - Clay__CustomElementConfigArray customElementConfigs; - Clay__BorderElementConfigArray borderElementConfigs; - Clay__SharedElementConfigArray sharedElementConfigs; // Misc Data Structures Clay__StringArray layoutElementIdStrings; Clay__WrappedTextLineArray wrappedTextLines; @@ -1285,6 +1366,7 @@ struct Clay_Context { Clay__int32_tArray openClipElementStack; Clay_ElementIdArray pointerOverIds; Clay__ScrollContainerDataInternalArray scrollContainerDatas; + Clay__TransitionDataInternalArray transitionDatas; Clay__boolArray treeNodeVisited; Clay__charArray dynamicStringData; Clay__DebugElementDataArray debugElementData; @@ -1321,38 +1403,17 @@ Clay_LayoutElement* Clay__GetOpenLayoutElement(void) { return Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1)); } -uint32_t Clay__GetParentElementId(void) { - return Clay__GetOpenLayoutElement()->id; -} - -Clay_LayoutConfig * Clay__StoreLayoutConfig(Clay_LayoutConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &CLAY_LAYOUT_DEFAULT : Clay__LayoutConfigArray_Add(&Clay_GetCurrentContext()->layoutConfigs, config); } -Clay_TextElementConfig * Clay__StoreTextElementConfig(Clay_TextElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_TextElementConfig_DEFAULT : Clay__TextElementConfigArray_Add(&Clay_GetCurrentContext()->textElementConfigs, config); } -Clay_AspectRatioElementConfig * Clay__StoreAspectRatioElementConfig(Clay_AspectRatioElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_AspectRatioElementConfig_DEFAULT : Clay__AspectRatioElementConfigArray_Add(&Clay_GetCurrentContext()->aspectRatioElementConfigs, config); } -Clay_ImageElementConfig * Clay__StoreImageElementConfig(Clay_ImageElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_ImageElementConfig_DEFAULT : Clay__ImageElementConfigArray_Add(&Clay_GetCurrentContext()->imageElementConfigs, config); } -Clay_FloatingElementConfig * Clay__StoreFloatingElementConfig(Clay_FloatingElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_FloatingElementConfig_DEFAULT : Clay__FloatingElementConfigArray_Add(&Clay_GetCurrentContext()->floatingElementConfigs, config); } -Clay_CustomElementConfig * Clay__StoreCustomElementConfig(Clay_CustomElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_CustomElementConfig_DEFAULT : Clay__CustomElementConfigArray_Add(&Clay_GetCurrentContext()->customElementConfigs, config); } -Clay_ClipElementConfig * Clay__StoreClipElementConfig(Clay_ClipElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_ClipElementConfig_DEFAULT : Clay__ClipElementConfigArray_Add(&Clay_GetCurrentContext()->clipElementConfigs, config); } -Clay_BorderElementConfig * Clay__StoreBorderElementConfig(Clay_BorderElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_BorderElementConfig_DEFAULT : Clay__BorderElementConfigArray_Add(&Clay_GetCurrentContext()->borderElementConfigs, config); } -Clay_SharedElementConfig * Clay__StoreSharedElementConfig(Clay_SharedElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_SharedElementConfig_DEFAULT : Clay__SharedElementConfigArray_Add(&Clay_GetCurrentContext()->sharedElementConfigs, config); } - -Clay_ElementConfig Clay__AttachElementConfig(Clay_ElementConfigUnion config, Clay__ElementConfigType type) { +Clay_LayoutElement* Clay__GetParentElement(void) { Clay_Context* context = Clay_GetCurrentContext(); - if (context->booleanWarnings.maxElementsExceeded) { - return CLAY__INIT(Clay_ElementConfig) CLAY__DEFAULT_STRUCT; - } - Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); - openLayoutElement->elementConfigs.length++; - return *Clay__ElementConfigArray_Add(&context->elementConfigs, CLAY__INIT(Clay_ElementConfig) { .type = type, .config = config }); + return Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); } -Clay_ElementConfigUnion Clay__FindElementConfigWithType(Clay_LayoutElement *element, Clay__ElementConfigType type) { - for (int32_t i = 0; i < element->elementConfigs.length; i++) { - Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&element->elementConfigs, i); - if (config->type == type) { - return config->config; - } - } - return CLAY__INIT(Clay_ElementConfigUnion) { NULL }; +uint32_t Clay__GetParentElementId(void) { + return Clay__GetParentElement()->id; +} + +bool Clay__BorderHasAnyWidth(Clay_BorderElementConfig* borderConfig) { + return borderConfig->width.betweenChildren > 0 || borderConfig->width.left > 0 || borderConfig->width.right > 0 || borderConfig->width.top > 0 || borderConfig->width.bottom > 0; } Clay_ElementId Clay__HashNumber(const uint32_t offset, const uint32_t seed) { @@ -1720,7 +1781,7 @@ Clay_LayoutElementHashMapItem* Clay__AddHashMapItem(Clay_ElementId elementId, Cl if (context->layoutElementsHashMapInternal.length == context->layoutElementsHashMapInternal.capacity - 1) { return NULL; } - Clay_LayoutElementHashMapItem item = { .elementId = elementId, .layoutElement = layoutElement, .nextIndex = -1, .generation = context->generation + 1 }; + Clay_LayoutElementHashMapItem item = { .elementId = elementId, .layoutElement = layoutElement, .nextIndex = -1, .generation = context->generation + 1, .appearedThisFrame = true }; uint32_t hashBucket = elementId.id % context->layoutElementsHashMap.capacity; int32_t hashItemPrevious = -1; int32_t hashItemIndex = context->layoutElementsHashMap.internalArray[hashBucket]; @@ -1729,6 +1790,7 @@ Clay_LayoutElementHashMapItem* Clay__AddHashMapItem(Clay_ElementId elementId, Cl if (hashItem->elementId.id == elementId.id) { // Collision - resolve based on generation item.nextIndex = hashItem->nextIndex; if (hashItem->generation <= context->generation) { // First collision - assume this is the "same" element + hashItem->appearedThisFrame = hashItem->generation < context->generation; hashItem->elementId = elementId; // Make sure to copy this across. If the stringId reference has changed, we should update the hash item to use the new one. hashItem->generation = context->generation + 1; hashItem->layoutElement = layoutElement; @@ -1773,40 +1835,12 @@ Clay_LayoutElementHashMapItem *Clay__GetHashMapItem(uint32_t id) { return &Clay_LayoutElementHashMapItem_DEFAULT; } -Clay_ElementId Clay__GenerateIdForAnonymousElement(Clay_LayoutElement *openLayoutElement) { - Clay_Context* context = Clay_GetCurrentContext(); - Clay_LayoutElement *parentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); - uint32_t offset = parentElement->childrenOrTextContent.children.length + parentElement->floatingChildrenCount; - Clay_ElementId elementId = Clay__HashNumber(offset, parentElement->id); - openLayoutElement->id = elementId.id; - Clay__AddHashMapItem(elementId, openLayoutElement); - Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); - return elementId; -} - -bool Clay__ElementHasConfig(Clay_LayoutElement *layoutElement, Clay__ElementConfigType type) { - for (int32_t i = 0; i < layoutElement->elementConfigs.length; i++) { - if (Clay__ElementConfigArraySlice_Get(&layoutElement->elementConfigs, i)->type == type) { - return true; - } - } - return false; -} - void Clay__UpdateAspectRatioBox(Clay_LayoutElement *layoutElement) { - for (int32_t j = 0; j < layoutElement->elementConfigs.length; j++) { - Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&layoutElement->elementConfigs, j); - if (config->type == CLAY__ELEMENT_CONFIG_TYPE_ASPECT) { - Clay_AspectRatioElementConfig *aspectConfig = config->config.aspectRatioElementConfig; - if (aspectConfig->aspectRatio == 0) { - break; - } - if (layoutElement->dimensions.width == 0 && layoutElement->dimensions.height != 0) { - layoutElement->dimensions.width = layoutElement->dimensions.height * aspectConfig->aspectRatio; - } else if (layoutElement->dimensions.width != 0 && layoutElement->dimensions.height == 0) { - layoutElement->dimensions.height = layoutElement->dimensions.width * (1 / aspectConfig->aspectRatio); - } - break; + if (layoutElement->config.aspectRatio.aspectRatio != 0) { + if (layoutElement->dimensions.width == 0 && layoutElement->dimensions.height != 0) { + layoutElement->dimensions.width = layoutElement->dimensions.height * layoutElement->config.aspectRatio.aspectRatio; + } else if (layoutElement->dimensions.width != 0 && layoutElement->dimensions.height == 0) { + layoutElement->dimensions.height = layoutElement->dimensions.width * (1 / layoutElement->config.aspectRatio.aspectRatio); } } } @@ -1817,35 +1851,23 @@ void Clay__CloseElement(void) { return; } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); - Clay_LayoutConfig *layoutConfig = openLayoutElement->layoutConfig; - if (!layoutConfig) { - openLayoutElement->layoutConfig = &Clay_LayoutConfig_DEFAULT; - layoutConfig = &Clay_LayoutConfig_DEFAULT; - } - bool elementHasClipHorizontal = false; - bool elementHasClipVertical = false; - for (int32_t i = 0; i < openLayoutElement->elementConfigs.length; i++) { - Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&openLayoutElement->elementConfigs, i); - if (config->type == CLAY__ELEMENT_CONFIG_TYPE_CLIP) { - elementHasClipHorizontal = config->config.clipElementConfig->horizontal; - elementHasClipVertical = config->config.clipElementConfig->vertical; - context->openClipElementStack.length--; - break; - } else if (config->type == CLAY__ELEMENT_CONFIG_TYPE_FLOATING) { - context->openClipElementStack.length--; - } + Clay_LayoutConfig *layoutConfig = &openLayoutElement->config.layout; + bool elementHasClipHorizontal = openLayoutElement->config.clip.horizontal; + bool elementHasClipVertical = openLayoutElement->config.clip.vertical; + if (elementHasClipHorizontal || elementHasClipVertical || openLayoutElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { + context->openClipElementStack.length--; } float leftRightPadding = (float)(layoutConfig->padding.left + layoutConfig->padding.right); float topBottomPadding = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom); // Attach children to the current open element - openLayoutElement->childrenOrTextContent.children.elements = &context->layoutElementChildren.internalArray[context->layoutElementChildren.length]; + openLayoutElement->children.elements = &context->layoutElementChildren.internalArray[context->layoutElementChildren.length]; if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { openLayoutElement->dimensions.width = leftRightPadding; openLayoutElement->minDimensions.width = leftRightPadding; - for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) { - int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i); + for (int32_t i = 0; i < openLayoutElement->children.length; i++) { + int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->children.length + i); Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex); openLayoutElement->dimensions.width += child->dimensions.width; openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + topBottomPadding); @@ -1858,7 +1880,7 @@ void Clay__CloseElement(void) { } Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); } - float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + float childGap = (float)(CLAY__MAX(openLayoutElement->children.length - 1, 0) * layoutConfig->childGap); openLayoutElement->dimensions.width += childGap; if (!elementHasClipHorizontal) { openLayoutElement->minDimensions.width += childGap; @@ -1867,8 +1889,8 @@ void Clay__CloseElement(void) { else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { openLayoutElement->dimensions.height = topBottomPadding; openLayoutElement->minDimensions.height = topBottomPadding; - for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) { - int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i); + for (int32_t i = 0; i < openLayoutElement->children.length; i++) { + int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->children.length + i); Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex); openLayoutElement->dimensions.height += child->dimensions.height; openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + leftRightPadding); @@ -1881,14 +1903,14 @@ void Clay__CloseElement(void) { } Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); } - float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + float childGap = (float)(CLAY__MAX(openLayoutElement->children.length - 1, 0) * layoutConfig->childGap); openLayoutElement->dimensions.height += childGap; if (!elementHasClipVertical) { openLayoutElement->minDimensions.height += childGap; } } - context->layoutElementChildrenBuffer.length -= openLayoutElement->childrenOrTextContent.children.length; + context->layoutElementChildrenBuffer.length -= openLayoutElement->children.length; // Clamp element min and max width to the values configured in the layout if (layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { @@ -1914,7 +1936,7 @@ void Clay__CloseElement(void) { Clay__UpdateAspectRatioBox(openLayoutElement); - bool elementIsFloating = Clay__ElementHasConfig(openLayoutElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING); + bool elementIsFloating = openLayoutElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE; // Close the currently open element int32_t closingElementIndex = Clay__int32_tArray_RemoveSwapback(&context->openLayoutElementStack, (int)context->openLayoutElementStack.length - 1); @@ -1927,7 +1949,7 @@ void Clay__CloseElement(void) { openLayoutElement->floatingChildrenCount++; return; } - openLayoutElement->childrenOrTextContent.children.length++; + openLayoutElement->children.length++; Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, closingElementIndex); } } @@ -2006,7 +2028,13 @@ void Clay__OpenElement(void) { Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; Clay_LayoutElement* openLayoutElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); Clay__int32_tArray_Add(&context->openLayoutElementStack, context->layoutElements.length - 1); - Clay__GenerateIdForAnonymousElement(openLayoutElement); + // Generate an ID + Clay_LayoutElement *parentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); + uint32_t offset = parentElement->children.length + parentElement->floatingChildrenCount; + Clay_ElementId elementId = Clay__HashNumber(offset, parentElement->id); + openLayoutElement->id = elementId.id; + Clay__AddHashMapItem(elementId, openLayoutElement); + Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); if (context->openClipElementStack.length > 0) { Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); } else { @@ -2033,7 +2061,7 @@ void Clay__OpenElementWithId(Clay_ElementId elementId) { } } -void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig) { +void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig textConfig) { Clay_Context* context = Clay_GetCurrentContext(); if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { context->booleanWarnings.maxElementsExceeded = true; @@ -2041,7 +2069,7 @@ void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig) } Clay_LayoutElement *parentElement = Clay__GetOpenLayoutElement(); - Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; + Clay_LayoutElement layoutElement = { .textConfig = textConfig, .isTextElement = true }; Clay_LayoutElement *textElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); if (context->openClipElementStack.length > 0) { Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); @@ -2050,27 +2078,22 @@ void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig) } Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, context->layoutElements.length - 1); - Clay__MeasureTextCacheItem *textMeasured = Clay__MeasureTextCached(&text, textConfig); - Clay_ElementId elementId = Clay__HashNumber(parentElement->childrenOrTextContent.children.length + parentElement->floatingChildrenCount, parentElement->id); + Clay__MeasureTextCacheItem *textMeasured = Clay__MeasureTextCached(&text, &textConfig); + Clay_ElementId elementId = Clay__HashNumber(parentElement->children.length + parentElement->floatingChildrenCount, parentElement->id); textElement->id = elementId.id; Clay__AddHashMapItem(elementId, textElement); Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); - Clay_Dimensions textDimensions = { .width = textMeasured->unwrappedDimensions.width, .height = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textMeasured->unwrappedDimensions.height }; + Clay_Dimensions textDimensions = { .width = textMeasured->unwrappedDimensions.width, .height = textConfig.lineHeight > 0 ? (float)textConfig.lineHeight : textMeasured->unwrappedDimensions.height }; textElement->dimensions = textDimensions; textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->minWidth, .height = textDimensions.height }; - textElement->childrenOrTextContent.textElementData = Clay__TextElementDataArray_Add(&context->textElementData, CLAY__INIT(Clay__TextElementData) { .text = text, .preferredDimensions = textMeasured->unwrappedDimensions, .elementIndex = context->layoutElements.length - 1 }); - textElement->elementConfigs = CLAY__INIT(Clay__ElementConfigArraySlice) { - .length = 1, - .internalArray = Clay__ElementConfigArray_Add(&context->elementConfigs, CLAY__INIT(Clay_ElementConfig) { .type = CLAY__ELEMENT_CONFIG_TYPE_TEXT, .config = { .textElementConfig = textConfig }}) - }; - textElement->layoutConfig = &CLAY_LAYOUT_DEFAULT; - parentElement->childrenOrTextContent.children.length++; + textElement->textElementData = CLAY__INIT(Clay__TextElementData) { .text = text, .preferredDimensions = textMeasured->unwrappedDimensions }; + parentElement->children.length++; } void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) { Clay_Context* context = Clay_GetCurrentContext(); Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); - openLayoutElement->layoutConfig = Clay__StoreLayoutConfig(declaration->layout); + openLayoutElement->config = *declaration; if ((declaration->layout.sizing.width.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.width.size.percent > 1) || (declaration->layout.sizing.height.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.height.size.percent > 1)) { context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_PERCENTAGE_OVER_1, @@ -2078,49 +2101,20 @@ void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) { .userData = context->errorHandler.userData }); } - openLayoutElement->elementConfigs.internalArray = &context->elementConfigs.internalArray[context->elementConfigs.length]; - Clay_SharedElementConfig *sharedConfig = NULL; - if (declaration->backgroundColor.a > 0) { - sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .backgroundColor = declaration->backgroundColor }); - Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED); - } - if (!Clay__MemCmp((char *)(&declaration->cornerRadius), (char *)(&Clay__CornerRadius_DEFAULT), sizeof(Clay_CornerRadius))) { - if (sharedConfig) { - sharedConfig->cornerRadius = declaration->cornerRadius; - } else { - sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .cornerRadius = declaration->cornerRadius }); - Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED); - } - } - if (declaration->userData != 0) { - if (sharedConfig) { - sharedConfig->userData = declaration->userData; - } else { - sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .userData = declaration->userData }); - Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED); - } - } - if (declaration->image.imageData) { - Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = Clay__StoreImageElementConfig(declaration->image) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE); - } - if (declaration->aspectRatio.aspectRatio > 0) { - Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .aspectRatioElementConfig = Clay__StoreAspectRatioElementConfig(declaration->aspectRatio) }, CLAY__ELEMENT_CONFIG_TYPE_ASPECT); - Clay__int32_tArray_Add(&context->aspectRatioElementIndexes, context->layoutElements.length - 1); - } if (declaration->floating.attachTo != CLAY_ATTACH_TO_NONE) { - Clay_FloatingElementConfig floatingConfig = declaration->floating; + Clay_FloatingElementConfig* floatingConfig = &openLayoutElement->config.floating; // This looks dodgy but because of the auto generated root element the depth of the tree will always be at least 2 here Clay_LayoutElement *hierarchicalParent = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); if (hierarchicalParent) { uint32_t clipElementId = 0; if (declaration->floating.attachTo == CLAY_ATTACH_TO_PARENT) { // Attach to the element's direct hierarchical parent - floatingConfig.parentId = hierarchicalParent->id; + floatingConfig->parentId = hierarchicalParent->id; if (context->openClipElementStack.length > 0) { clipElementId = Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1); } } else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) { - Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingConfig.parentId); + Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingConfig->parentId); if (parentItem == &Clay_LayoutElementHashMapItem_DEFAULT) { context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { .errorType = CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, @@ -2130,7 +2124,7 @@ void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) { clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(parentItem->layoutElement - context->layoutElements.internalArray)); } } else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ROOT) { - floatingConfig.parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0).id; + floatingConfig->parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0).id; } if (declaration->floating.clipTo == CLAY_CLIP_TO_NONE) { clipElementId = 0; @@ -2139,20 +2133,15 @@ void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) { Clay__int32_tArray_Set(&context->layoutElementClipElementIds, currentElementIndex, clipElementId); Clay__int32_tArray_Add(&context->openClipElementStack, clipElementId); Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { - .layoutElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1), - .parentId = floatingConfig.parentId, - .clipElementId = clipElementId, - .zIndex = floatingConfig.zIndex, + .layoutElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1), + .parentId = floatingConfig->parentId, + .clipElementId = clipElementId, + .zIndex = floatingConfig->zIndex, }); - Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .floatingElementConfig = Clay__StoreFloatingElementConfig(floatingConfig) }, CLAY__ELEMENT_CONFIG_TYPE_FLOATING); } } - if (declaration->custom.customData) { - Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration->custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM); - } - if (declaration->clip.horizontal | declaration->clip.vertical) { - Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .clipElementConfig = Clay__StoreClipElementConfig(declaration->clip) }, CLAY__ELEMENT_CONFIG_TYPE_CLIP); + if (declaration->clip.horizontal || declaration->clip.vertical) { Clay__int32_tArray_Add(&context->openClipElementStack, (int)openLayoutElement->id); // Retrieve or create cached data to track scroll position across frames Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL; @@ -2171,8 +2160,37 @@ void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) { scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId, context->queryScrollOffsetUserData); } } - if (!Clay__MemCmp((char *)(&declaration->border.width), (char *)(&Clay__BorderWidth_DEFAULT), sizeof(Clay_BorderWidth))) { - Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .borderElementConfig = Clay__StoreBorderElementConfig(declaration->border) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER); + // Setup data to track transitions across frames + if (declaration->transition.handler) { + Clay__TransitionDataInternal *transitionData = CLAY__NULL; + Clay_LayoutElement* parentElement = Clay__GetParentElement(); + for (int32_t i = 0; i < context->transitionDatas.length; i++) { + Clay__TransitionDataInternal *existingData = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, i); + if (openLayoutElement->id == existingData->elementId) { + if (existingData->state == CLAY_TRANSITION_STATE_EXITING) { + existingData->state = CLAY_TRANSITION_STATE_IDLE; + Clay_LayoutElementHashMapItem* hashMapItem = Clay__GetHashMapItem(openLayoutElement->id); + hashMapItem->appearedThisFrame = false; + } + transitionData = existingData; + transitionData->elementThisFrame = openLayoutElement; + if (transitionData->parentId != parentElement->id) { + transitionData->reparented = true; + } + transitionData->parentId = parentElement->id; + transitionData->siblingIndex = parentElement->children.length; + transitionData->transitionOut = !!declaration->transition.exit.setFinalState; + } + } + if (!transitionData) { + transitionData = Clay__TransitionDataInternalArray_Add(&context->transitionDatas, CLAY__INIT(Clay__TransitionDataInternal){ + .elementThisFrame = openLayoutElement, + .elementId = openLayoutElement->id, + .parentId = parentElement->id, + .siblingIndex = parentElement->children.length, + .transitionOut = !!declaration->transition.exit.setFinalState + }); + } } } @@ -2190,25 +2208,12 @@ void Clay__InitializeEphemeralMemory(Clay_Context* context) { context->layoutElements = Clay_LayoutElementArray_Allocate_Arena(maxElementCount, arena); context->warnings = Clay__WarningArray_Allocate_Arena(100, arena); - context->layoutConfigs = Clay__LayoutConfigArray_Allocate_Arena(maxElementCount, arena); - context->elementConfigs = Clay__ElementConfigArray_Allocate_Arena(maxElementCount, arena); - context->textElementConfigs = Clay__TextElementConfigArray_Allocate_Arena(maxElementCount, arena); - context->aspectRatioElementConfigs = Clay__AspectRatioElementConfigArray_Allocate_Arena(maxElementCount, arena); - context->imageElementConfigs = Clay__ImageElementConfigArray_Allocate_Arena(maxElementCount, arena); - context->floatingElementConfigs = Clay__FloatingElementConfigArray_Allocate_Arena(maxElementCount, arena); - context->clipElementConfigs = Clay__ClipElementConfigArray_Allocate_Arena(maxElementCount, arena); - context->customElementConfigs = Clay__CustomElementConfigArray_Allocate_Arena(maxElementCount, arena); - context->borderElementConfigs = Clay__BorderElementConfigArray_Allocate_Arena(maxElementCount, arena); - context->sharedElementConfigs = Clay__SharedElementConfigArray_Allocate_Arena(maxElementCount, arena); - context->layoutElementIdStrings = Clay__StringArray_Allocate_Arena(maxElementCount, arena); context->wrappedTextLines = Clay__WrappedTextLineArray_Allocate_Arena(maxElementCount, arena); context->layoutElementTreeNodeArray1 = Clay__LayoutElementTreeNodeArray_Allocate_Arena(maxElementCount, arena); context->layoutElementTreeRoots = Clay__LayoutElementTreeRootArray_Allocate_Arena(maxElementCount, arena); context->layoutElementChildren = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->openLayoutElementStack = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); - context->textElementData = Clay__TextElementDataArray_Allocate_Arena(maxElementCount, arena); - context->aspectRatioElementIndexes = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->renderCommands = Clay_RenderCommandArray_Allocate_Arena(maxElementCount, arena); context->treeNodeVisited = Clay__boolArray_Allocate_Arena(maxElementCount, arena); context->treeNodeVisited.length = context->treeNodeVisited.capacity; // This array is accessed directly rather than behaving as a list @@ -2225,6 +2230,7 @@ void Clay__InitializePersistentMemory(Clay_Context* context) { Clay_Arena *arena = &context->internalArena; context->scrollContainerDatas = Clay__ScrollContainerDataInternalArray_Allocate_Arena(100, arena); + context->transitionDatas = Clay__TransitionDataInternalArray_Allocate_Arena(200, arena); context->layoutElementsHashMapInternal = Clay__LayoutElementHashMapItemArray_Allocate_Arena(maxElementCount, arena); context->layoutElementsHashMap = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->measureTextHashMapInternal = Clay__MeasureTextCacheItemArray_Allocate_Arena(maxElementCount, arena); @@ -2244,7 +2250,8 @@ bool Clay__FloatEqual(float left, float right) { return subtracted < CLAY__EPSILON && subtracted > -CLAY__EPSILON; } -void Clay__SizeContainersAlongAxis(bool xAxis) { +// Writes out the location of text elements to layout elements buffer 1 +void Clay__SizeContainersAlongAxis(bool xAxis, float deltaTime, Clay__int32_tArray* textElementsOut, Clay__int32_tArray* aspectRatioElementsOut) { Clay_Context* context = Clay_GetCurrentContext(); Clay__int32_tArray bfsBuffer = context->layoutElementChildrenBuffer; Clay__int32_tArray resizableContainerBuffer = context->openLayoutElementStack; @@ -2255,29 +2262,29 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { Clay__int32_tArray_Add(&bfsBuffer, (int32_t)root->layoutElementIndex); // Size floating containers to their parents - if (Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING)) { - Clay_FloatingElementConfig *floatingElementConfig = Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig; + if (rootElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { + Clay_FloatingElementConfig *floatingElementConfig = &rootElement->config.floating; Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingElementConfig->parentId); if (parentItem && parentItem != &Clay_LayoutElementHashMapItem_DEFAULT) { Clay_LayoutElement *parentLayoutElement = parentItem->layoutElement; - switch (rootElement->layoutConfig->sizing.width.type) { + switch (rootElement->config.layout.sizing.width.type) { case CLAY__SIZING_TYPE_GROW: { rootElement->dimensions.width = parentLayoutElement->dimensions.width; break; } case CLAY__SIZING_TYPE_PERCENT: { - rootElement->dimensions.width = parentLayoutElement->dimensions.width * rootElement->layoutConfig->sizing.width.size.percent; + rootElement->dimensions.width = parentLayoutElement->dimensions.width * rootElement->config.layout.sizing.width.size.percent; break; } default: break; } - switch (rootElement->layoutConfig->sizing.height.type) { + switch (rootElement->config.layout.sizing.height.type) { case CLAY__SIZING_TYPE_GROW: { rootElement->dimensions.height = parentLayoutElement->dimensions.height; break; } case CLAY__SIZING_TYPE_PERCENT: { - rootElement->dimensions.height = parentLayoutElement->dimensions.height * rootElement->layoutConfig->sizing.height.size.percent; + rootElement->dimensions.height = parentLayoutElement->dimensions.height * rootElement->config.layout.sizing.height.size.percent; break; } default: break; @@ -2285,38 +2292,51 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { } } - if (rootElement->layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { - rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->layoutConfig->sizing.width.size.minMax.min), rootElement->layoutConfig->sizing.width.size.minMax.max); + if (rootElement->config.layout.sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { + rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->config.layout.sizing.width.size.minMax.min), rootElement->config.layout.sizing.width.size.minMax.max); } - if (rootElement->layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { - rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->layoutConfig->sizing.height.size.minMax.min), rootElement->layoutConfig->sizing.height.size.minMax.max); + if (rootElement->config.layout.sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { + rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->config.layout.sizing.height.size.minMax.min), rootElement->config.layout.sizing.height.size.minMax.max); } + for (int32_t i = 0; i < bfsBuffer.length; ++i) { int32_t parentIndex = Clay__int32_tArray_GetValue(&bfsBuffer, i); Clay_LayoutElement *parent = Clay_LayoutElementArray_Get(&context->layoutElements, parentIndex); - Clay_LayoutConfig *parentStyleConfig = parent->layoutConfig; + Clay_LayoutConfig *parentLayoutConfig = &parent->config.layout; int32_t growContainerCount = 0; float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height; - float parentPadding = (float)(xAxis ? (parent->layoutConfig->padding.left + parent->layoutConfig->padding.right) : (parent->layoutConfig->padding.top + parent->layoutConfig->padding.bottom)); + float parentPadding = (float)(xAxis ? (parentLayoutConfig->padding.left + parentLayoutConfig->padding.right) : (parentLayoutConfig->padding.top + parentLayoutConfig->padding.bottom)); float innerContentSize = 0, totalPaddingAndChildGaps = parentPadding; - bool sizingAlongAxis = (xAxis && parentStyleConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentStyleConfig->layoutDirection == CLAY_TOP_TO_BOTTOM); + bool sizingAlongAxis = (xAxis && parentLayoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentLayoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM); resizableContainerBuffer.length = 0; - float parentChildGap = parentStyleConfig->childGap; + float parentChildGap = parentLayoutConfig->childGap; + bool isFirstChild = true; - for (int32_t childOffset = 0; childOffset < parent->childrenOrTextContent.children.length; childOffset++) { - int32_t childElementIndex = parent->childrenOrTextContent.children.elements[childOffset]; + for (int32_t childOffset = 0; childOffset < parent->children.length; childOffset++) { + int32_t childElementIndex = parent->children.elements[childOffset]; Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childElementIndex); - Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + Clay_SizingAxis childSizing = xAxis ? childElement->config.layout.sizing.width : childElement->config.layout.sizing.height; float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height; - if (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && childElement->childrenOrTextContent.children.length > 0) { + if (textElementsOut && childElement->isTextElement) { + Clay__int32_tArray_Add(textElementsOut, childElementIndex); + } else if (childElement->children.length > 0) { Clay__int32_tArray_Add(&bfsBuffer, childElementIndex); } + if (aspectRatioElementsOut && childElement->config.aspectRatio.aspectRatio != 0) { + Clay__int32_tArray_Add(aspectRatioElementsOut, childElementIndex); + } + + // Note: setting isFirstChild = false is skipped here + if (childElement->exiting) { + continue; + } + if (childSizing.type != CLAY__SIZING_TYPE_PERCENT && childSizing.type != CLAY__SIZING_TYPE_FIXED - && (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (Clay__FindElementConfigWithType(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig->wrapMode == CLAY_TEXT_WRAP_WORDS)) // todo too many loops + && (!childElement->isTextElement || childElement->textConfig.wrapMode == CLAY_TEXT_WRAP_WORDS) // && (xAxis || !Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT)) ) { Clay__int32_tArray_Add(&resizableContainerBuffer, childElementIndex); @@ -2327,20 +2347,21 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { if (childSizing.type == CLAY__SIZING_TYPE_GROW) { growContainerCount++; } - if (childOffset > 0) { + if (!isFirstChild) { innerContentSize += parentChildGap; // For children after index 0, the childAxisOffset is the gap from the previous child totalPaddingAndChildGaps += parentChildGap; } } else { innerContentSize = CLAY__MAX(childSize, innerContentSize); } + isFirstChild = false; } // Expand percentage containers to size - for (int32_t childOffset = 0; childOffset < parent->childrenOrTextContent.children.length; childOffset++) { - int32_t childElementIndex = parent->childrenOrTextContent.children.elements[childOffset]; + for (int32_t childOffset = 0; childOffset < parent->children.length; childOffset++) { + int32_t childElementIndex = parent->children.elements[childOffset]; Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childElementIndex); - Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + Clay_SizingAxis childSizing = xAxis ? childElement->config.layout.sizing.width : childElement->config.layout.sizing.height; float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; if (childSizing.type == CLAY__SIZING_TYPE_PERCENT) { *childSize = (parentSize - totalPaddingAndChildGaps) * childSizing.size.percent; @@ -2356,11 +2377,8 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { // The content is too large, compress the children as much as possible if (sizeToDistribute < 0) { // If the parent clips content in this axis direction, don't compress children, just leave them alone - Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; - if (clipElementConfig) { - if (((xAxis && clipElementConfig->horizontal) || (!xAxis && clipElementConfig->vertical))) { - continue; - } + if (((xAxis && parent->config.clip.horizontal) || (!xAxis && parent->config.clip.vertical))) { + continue; } // Scrolling containers preferentially compress before others while (sizeToDistribute < -CLAY__EPSILON && resizableContainerBuffer.length > 0) { @@ -2402,7 +2420,7 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { } else if (sizeToDistribute > 0 && growContainerCount > 0) { for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); - Clay__SizingType childSizing = xAxis ? child->layoutConfig->sizing.width.type : child->layoutConfig->sizing.height.type; + Clay__SizingType childSizing = xAxis ? child->config.layout.sizing.width.type : child->config.layout.sizing.height.type; if (childSizing != CLAY__SIZING_TYPE_GROW) { Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); } @@ -2430,7 +2448,7 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height; - float maxSize = xAxis ? child->layoutConfig->sizing.width.size.minMax.max : child->layoutConfig->sizing.height.size.minMax.max; + float maxSize = xAxis ? child->config.layout.sizing.width.size.minMax.max : child->config.layout.sizing.height.size.minMax.max; float previousWidth = *childSize; if (Clay__FloatEqual(*childSize, smallest)) { *childSize += widthToAdd; @@ -2447,17 +2465,14 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { } else { for (int32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childOffset)); - Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + Clay_SizingAxis childSizing = xAxis ? childElement->config.layout.sizing.width : childElement->config.layout.sizing.height; float minSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height; float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; float maxSize = parentSize - parentPadding; // If we're laying out the children of a scroll panel, grow containers expand to the size of the inner content, not the outer container - if (Clay__ElementHasConfig(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP)) { - Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; - if (((xAxis && clipElementConfig->horizontal) || (!xAxis && clipElementConfig->vertical))) { - maxSize = CLAY__MAX(maxSize, innerContentSize); - } + if (((xAxis && parent->config.clip.horizontal) || (!xAxis && parent->config.clip.vertical))) { + maxSize = CLAY__MAX(maxSize, innerContentSize); } if (childSizing.type == CLAY__SIZING_TYPE_GROW) { *childSize = CLAY__MIN(maxSize, childSizing.size.minMax.max); @@ -2527,20 +2542,25 @@ bool Clay__ElementIsOffscreen(Clay_BoundingBox *boundingBox) { (boundingBox->y + boundingBox->height < 0); } -void Clay__CalculateFinalLayout(void) { +void Clay__CalculateFinalLayout(float deltaTime, bool useStoredBoundingBoxes, bool generateRenderCommands) { Clay_Context* context = Clay_GetCurrentContext(); + // Calculate sizing along the X axis - Clay__SizeContainersAlongAxis(true); + Clay__int32_tArray textElements = context->openClipElementStack; + textElements.length = 0; + Clay__int32_tArray aspectRatioElements = context->reusableElementIndexBuffer; + aspectRatioElements.length = 0; + Clay__SizeContainersAlongAxis(true, deltaTime, &textElements, &aspectRatioElements); // Wrap text - for (int32_t textElementIndex = 0; textElementIndex < context->textElementData.length; ++textElementIndex) { - Clay__TextElementData *textElementData = Clay__TextElementDataArray_Get(&context->textElementData, textElementIndex); + for (int32_t textElementIndex = 0; textElementIndex < textElements.length; ++textElementIndex) { + Clay_LayoutElement *element = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&textElements, textElementIndex)); + Clay__TextElementData *textElementData = &element->textElementData; textElementData->wrappedLines = CLAY__INIT(Clay__WrappedTextLineArraySlice) { .length = 0, .internalArray = &context->wrappedTextLines.internalArray[context->wrappedTextLines.length] }; - Clay_LayoutElement *containerElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)textElementData->elementIndex); - Clay_TextElementConfig *textConfig = Clay__FindElementConfigWithType(containerElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig; - Clay__MeasureTextCacheItem *measureTextCacheItem = Clay__MeasureTextCached(&textElementData->text, textConfig); + Clay_LayoutElement *containerElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&textElements, textElementIndex)); + Clay__MeasureTextCacheItem *measureTextCacheItem = Clay__MeasureTextCached(&textElementData->text, &containerElement->textConfig); float lineWidth = 0; - float lineHeight = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textElementData->preferredDimensions.height; + float lineHeight = containerElement->textConfig.lineHeight > 0 ? (float)containerElement->textConfig.lineHeight : textElementData->preferredDimensions.height; int32_t lineLengthChars = 0; int32_t lineStartOffset = 0; if (!measureTextCacheItem->containsNewlines && textElementData->preferredDimensions.width <= containerElement->dimensions.width) { @@ -2548,7 +2568,7 @@ void Clay__CalculateFinalLayout(void) { textElementData->wrappedLines.length++; continue; } - float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, textConfig, context->measureTextUserData).width; + float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, &containerElement->textConfig, context->measureTextUserData).width; int32_t wordIndex = measureTextCacheItem->measuredWordsStartIndex; while (wordIndex != -1) { if (context->wrappedTextLines.length > context->wrappedTextLines.capacity - 1) { @@ -2575,24 +2595,23 @@ void Clay__CalculateFinalLayout(void) { lineLengthChars = 0; lineStartOffset = measuredWord->startOffset; } else { - lineWidth += measuredWord->width + textConfig->letterSpacing; + lineWidth += measuredWord->width + containerElement->textConfig.letterSpacing; lineLengthChars += measuredWord->length; wordIndex = measuredWord->next; } } if (lineLengthChars > 0) { - Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth - textConfig->letterSpacing, lineHeight }, {.length = lineLengthChars, .chars = &textElementData->text.chars[lineStartOffset] } }); + Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth - containerElement->textConfig.letterSpacing, lineHeight }, {.length = lineLengthChars, .chars = &textElementData->text.chars[lineStartOffset] } }); textElementData->wrappedLines.length++; } containerElement->dimensions.height = lineHeight * (float)textElementData->wrappedLines.length; } // Scale vertical heights according to aspect ratio - for (int32_t i = 0; i < context->aspectRatioElementIndexes.length; ++i) { - Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->aspectRatioElementIndexes, i)); - Clay_AspectRatioElementConfig *config = Clay__FindElementConfigWithType(aspectElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; - aspectElement->dimensions.height = (1 / config->aspectRatio) * aspectElement->dimensions.width; - aspectElement->layoutConfig->sizing.height.size.minMax.max = aspectElement->dimensions.height; + for (int32_t i = 0; i < aspectRatioElements.length; ++i) { + Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&aspectRatioElements, i)); + aspectElement->dimensions.height = (1 / aspectElement->config.aspectRatio.aspectRatio) * aspectElement->dimensions.width; + aspectElement->config.layout.sizing.height.size.minMax.max = aspectElement->dimensions.height; } // Propagate effect of text wrapping, aspect scaling etc. on height of parents @@ -2609,48 +2628,47 @@ void Clay__CalculateFinalLayout(void) { if (!context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; // If the element has no children or is the container for a text element, don't bother inspecting it - if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0) { + if (currentElement->isTextElement || currentElement->children.length == 0) { dfsBuffer.length--; continue; } // Add the children to the DFS buffer (needs to be pushed in reverse so that stack traversal is in correct layout order) - for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; i++) { + for (int32_t i = 0; i < currentElement->children.length; i++) { context->treeNodeVisited.internalArray[dfsBuffer.length] = false; - Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]) }); + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&context->layoutElements, 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; + Clay_LayoutConfig *layoutConfig = ¤tElement->config.layout; if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { // Resize any parent containers that have grown in height along their non layout axis - for (int32_t j = 0; j < currentElement->childrenOrTextContent.children.length; ++j) { - Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[j]); + for (int32_t j = 0; j < currentElement->children.length; ++j) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[j]); float childHeightWithPadding = CLAY__MAX(childElement->dimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom, currentElement->dimensions.height); currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(childHeightWithPadding, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); } } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { // Resizing along the layout axis float contentHeight = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom); - for (int32_t j = 0; j < currentElement->childrenOrTextContent.children.length; ++j) { - Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[j]); + for (int32_t j = 0; j < currentElement->children.length; ++j) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[j]); contentHeight += childElement->dimensions.height; } - contentHeight += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + contentHeight += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap); currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(contentHeight, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); } } // Calculate sizing along the Y axis - Clay__SizeContainersAlongAxis(false); + Clay__SizeContainersAlongAxis(false, deltaTime, NULL, NULL); // Scale horizontal widths according to aspect ratio - for (int32_t i = 0; i < context->aspectRatioElementIndexes.length; ++i) { - Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->aspectRatioElementIndexes, i)); - Clay_AspectRatioElementConfig *config = Clay__FindElementConfigWithType(aspectElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; - aspectElement->dimensions.width = config->aspectRatio * aspectElement->dimensions.height; + for (int32_t i = 0; i < aspectRatioElements.length; ++i) { + Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&aspectRatioElements, i)); + aspectElement->dimensions.width = aspectElement->config.aspectRatio.aspectRatio * aspectElement->dimensions.height; } // Sort tree roots by z-index @@ -2670,6 +2688,7 @@ void Clay__CalculateFinalLayout(void) { // Calculate final positions and generate render commands context->renderCommands.length = 0; dfsBuffer.length = 0; + for (int32_t rootIndex = 0; rootIndex < context->layoutElementTreeRoots.length; ++rootIndex) { dfsBuffer.length = 0; Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); @@ -2677,8 +2696,8 @@ void Clay__CalculateFinalLayout(void) { Clay_Vector2 rootPosition = CLAY__DEFAULT_STRUCT; Clay_LayoutElementHashMapItem *parentHashMapItem = Clay__GetHashMapItem(root->parentId); // Position root floating containers - if (Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING) && parentHashMapItem) { - Clay_FloatingElementConfig *config = Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig; + if (rootElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE && parentHashMapItem) { + Clay_FloatingElementConfig *config = &rootElement->config.floating; Clay_Dimensions rootDimensions = rootElement->dimensions; Clay_BoundingBox parentBoundingBox = parentHashMapItem->boundingBox; // Set X position @@ -2733,338 +2752,107 @@ void Clay__CalculateFinalLayout(void) { } if (root->clipElementId) { Clay_LayoutElementHashMapItem *clipHashMapItem = Clay__GetHashMapItem(root->clipElementId); - if (clipHashMapItem) { + if (clipHashMapItem && !Clay__ElementIsOffscreen(&clipHashMapItem->boundingBox)) { // Floating elements that are attached to scrolling contents won't be correctly positioned if external scroll handling is enabled, fix here if (context->externalScrollHandlingEnabled) { - Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(clipHashMapItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; - if (clipConfig->horizontal) { - rootPosition.x += clipConfig->childOffset.x; + if (clipHashMapItem->layoutElement->config.clip.horizontal) { + rootPosition.x += clipHashMapItem->layoutElement->config.clip.childOffset.x; } - if (clipConfig->vertical) { - rootPosition.y += clipConfig->childOffset.y; + if (clipHashMapItem->layoutElement->config.clip.vertical) { + rootPosition.y += clipHashMapItem->layoutElement->config.clip.childOffset.y; } } - Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { - .boundingBox = clipHashMapItem->boundingBox, - .userData = 0, - .id = Clay__HashNumber(rootElement->id, rootElement->childrenOrTextContent.children.length + 10).id, // TODO need a better strategy for managing derived ids - .zIndex = root->zIndex, - .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, - }); + if (generateRenderCommands) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = clipHashMapItem->boundingBox, + .userData = 0, + .id = Clay__HashNumber(rootElement->id, rootElement->children.length + 10).id, // TODO need a better strategy for managing derived ids + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, + }); + } } } - Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = rootElement, .position = rootPosition, .nextChildOffset = { .x = (float)rootElement->layoutConfig->padding.left, .y = (float)rootElement->layoutConfig->padding.top } }); + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = rootElement, .position = rootPosition, .nextChildOffset = { .x = (float)rootElement->config.layout.padding.left, .y = (float)rootElement->config.layout.padding.top } }); context->treeNodeVisited.internalArray[0] = false; while (dfsBuffer.length > 0) { Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; - Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig; + Clay_LayoutConfig *layoutConfig = ¤tElement->config.layout; Clay_Vector2 scrollOffset = CLAY__DEFAULT_STRUCT; - // This will only be run a single time for each element in downwards DFS order - if (!context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { - context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; - - Clay_BoundingBox currentElementBoundingBox = { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height }; - if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING)) { - Clay_FloatingElementConfig *floatingElementConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig; - Clay_Dimensions expand = floatingElementConfig->expand; - currentElementBoundingBox.x -= expand.width; - currentElementBoundingBox.width += expand.width * 2; - currentElementBoundingBox.y -= expand.height; - currentElementBoundingBox.height += expand.height * 2; + // DFS is returning back upwards + if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + if (currentElement->isTextElement) { + dfsBuffer.length--; + continue; } - - Clay__ScrollContainerDataInternal *scrollContainerData = CLAY__NULL; - // Apply scroll offsets to container - if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP)) { - Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; - - // This linear scan could theoretically be slow under very strange conditions, but I can't imagine a real UI with more than a few 10's of scroll containers - for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { - Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); - if (mapping->layoutElement == currentElement) { - scrollContainerData = mapping; - mapping->boundingBox = currentElementBoundingBox; - scrollOffset = clipConfig->childOffset; - if (context->externalScrollHandlingEnabled) { - scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; - } - break; - } - } - } - - Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(currentElement->id); - if (hashMapItem) { - hashMapItem->boundingBox = currentElementBoundingBox; - } - - int32_t sortedConfigIndexes[20]; - for (int32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { - sortedConfigIndexes[elementConfigIndex] = elementConfigIndex; - } - sortMax = currentElement->elementConfigs.length - 1; - while (sortMax > 0) { // todo dumb bubble sort - for (int32_t i = 0; i < sortMax; ++i) { - int32_t current = sortedConfigIndexes[i]; - int32_t next = sortedConfigIndexes[i + 1]; - Clay__ElementConfigType currentType = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, current)->type; - Clay__ElementConfigType nextType = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, next)->type; - if (nextType == CLAY__ELEMENT_CONFIG_TYPE_CLIP || currentType == CLAY__ELEMENT_CONFIG_TYPE_BORDER) { - sortedConfigIndexes[i] = next; - sortedConfigIndexes[i + 1] = current; - } - } - sortMax--; - } - - bool emitRectangle = false; - // Create the render commands for this element - Clay_SharedElementConfig *sharedConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SHARED).sharedElementConfig; - if (sharedConfig && sharedConfig->backgroundColor.a > 0) { - emitRectangle = true; - } - else if (!sharedConfig) { - emitRectangle = false; - sharedConfig = &Clay_SharedElementConfig_DEFAULT; - } - for (int32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { - Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, sortedConfigIndexes[elementConfigIndex]); - Clay_RenderCommand renderCommand = { - .boundingBox = currentElementBoundingBox, - .userData = sharedConfig->userData, - .id = currentElement->id, - }; - - bool offscreen = Clay__ElementIsOffscreen(¤tElementBoundingBox); - // Culling - Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow - bool shouldRender = !offscreen; - switch (elementConfig->type) { - case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: - case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: - case CLAY__ELEMENT_CONFIG_TYPE_SHARED: - case CLAY__ELEMENT_CONFIG_TYPE_BORDER: { - shouldRender = false; - break; - } - case CLAY__ELEMENT_CONFIG_TYPE_CLIP: { - renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START; - renderCommand.renderData = CLAY__INIT(Clay_RenderData) { - .clip = { - .horizontal = elementConfig->config.clipElementConfig->horizontal, - .vertical = elementConfig->config.clipElementConfig->vertical, + Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); + if (generateRenderCommands && !Clay__ElementIsOffscreen(¤tElementData->boundingBox)) { + // DFS is returning upwards backwards + bool closeClipElement = false; + if (currentElement->config.clip.horizontal || currentElement->config.clip.vertical) { + closeClipElement = true; + for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); + if (mapping->layoutElement == currentElement) { + scrollOffset = currentElement->config.clip.childOffset; + if (context->externalScrollHandlingEnabled) { + scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; } - }; - break; - } - case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { - renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE; - renderCommand.renderData = CLAY__INIT(Clay_RenderData) { - .image = { - .backgroundColor = sharedConfig->backgroundColor, - .cornerRadius = sharedConfig->cornerRadius, - .imageData = elementConfig->config.imageElementConfig->imageData, - } - }; - emitRectangle = false; - break; - } - case CLAY__ELEMENT_CONFIG_TYPE_TEXT: { - if (!shouldRender) { break; } - shouldRender = false; - Clay_ElementConfigUnion configUnion = elementConfig->config; - Clay_TextElementConfig *textElementConfig = configUnion.textElementConfig; - float naturalLineHeight = currentElement->childrenOrTextContent.textElementData->preferredDimensions.height; - float finalLineHeight = textElementConfig->lineHeight > 0 ? (float)textElementConfig->lineHeight : naturalLineHeight; - float lineHeightOffset = (finalLineHeight - naturalLineHeight) / 2; - float yPosition = lineHeightOffset; - for (int32_t lineIndex = 0; lineIndex < currentElement->childrenOrTextContent.textElementData->wrappedLines.length; ++lineIndex) { - Clay__WrappedTextLine *wrappedLine = Clay__WrappedTextLineArraySlice_Get(¤tElement->childrenOrTextContent.textElementData->wrappedLines, lineIndex); - if (wrappedLine->line.length == 0) { - yPosition += finalLineHeight; - continue; - } - float offset = (currentElementBoundingBox.width - wrappedLine->dimensions.width); - if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_LEFT) { - offset = 0; - } - if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { - offset /= 2; - } - Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { - .boundingBox = { currentElementBoundingBox.x + offset, currentElementBoundingBox.y + yPosition, wrappedLine->dimensions.width, wrappedLine->dimensions.height }, - .renderData = { .text = { - .stringContents = CLAY__INIT(Clay_StringSlice) { .length = wrappedLine->line.length, .chars = wrappedLine->line.chars, .baseChars = currentElement->childrenOrTextContent.textElementData->text.chars }, - .textColor = textElementConfig->textColor, - .fontId = textElementConfig->fontId, - .fontSize = textElementConfig->fontSize, - .letterSpacing = textElementConfig->letterSpacing, - .lineHeight = textElementConfig->lineHeight, - }}, - .userData = textElementConfig->userData, - .id = Clay__HashNumber(lineIndex, currentElement->id).id, - .zIndex = root->zIndex, - .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT, - }); - yPosition += finalLineHeight; - - if (!context->disableCulling && (currentElementBoundingBox.y + yPosition > context->layoutDimensions.height)) { - break; - } - } - break; - } - case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: { - renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_CUSTOM; - renderCommand.renderData = CLAY__INIT(Clay_RenderData) { - .custom = { - .backgroundColor = sharedConfig->backgroundColor, - .cornerRadius = sharedConfig->cornerRadius, - .customData = elementConfig->config.customElementConfig->customData, - } - }; - emitRectangle = false; - break; - } - default: break; - } - if (shouldRender) { - Clay__AddRenderCommand(renderCommand); - } - if (offscreen) { - // NOTE: You may be tempted to try an early return / continue if an element is off screen. Why bother calculating layout for its children, right? - // Unfortunately, a FLOATING_CONTAINER may be defined that attaches to a child or grandchild of this element, which is large enough to still - // be on screen, even if this element isn't. That depends on this element and it's children being laid out correctly (even if they are entirely off screen) - } - } - - if (emitRectangle) { - Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { - .boundingBox = currentElementBoundingBox, - .renderData = { .rectangle = { - .backgroundColor = sharedConfig->backgroundColor, - .cornerRadius = sharedConfig->cornerRadius, - }}, - .userData = sharedConfig->userData, - .id = currentElement->id, - .zIndex = root->zIndex, - .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, - }); - } - - // Setup initial on-axis alignment - if (!Clay__ElementHasConfig(currentElementTreeNode->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { - Clay_Dimensions contentSize = {0,0}; - if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { - for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { - Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); - contentSize.width += childElement->dimensions.width; - contentSize.height = CLAY__MAX(contentSize.height, childElement->dimensions.height); - } - contentSize.width += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); - float extraSpace = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - contentSize.width; - switch (layoutConfig->childAlignment.x) { - case CLAY_ALIGN_X_LEFT: extraSpace = 0; break; - case CLAY_ALIGN_X_CENTER: extraSpace /= 2; break; - default: break; - } - currentElementTreeNode->nextChildOffset.x += extraSpace; - extraSpace = CLAY__MAX(0, extraSpace); - } else { - for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { - Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); - contentSize.width = CLAY__MAX(contentSize.width, childElement->dimensions.width); - contentSize.height += childElement->dimensions.height; - } - contentSize.height += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); - float extraSpace = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - contentSize.height; - switch (layoutConfig->childAlignment.y) { - case CLAY_ALIGN_Y_TOP: extraSpace = 0; break; - case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break; - default: break; - } - extraSpace = CLAY__MAX(0, extraSpace); - currentElementTreeNode->nextChildOffset.y += extraSpace; - } - - if (scrollContainerData) { - scrollContainerData->contentSize = CLAY__INIT(Clay_Dimensions) { contentSize.width + (float)(layoutConfig->padding.left + layoutConfig->padding.right), contentSize.height + (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) }; - } - } - } - else { - // DFS is returning upwards backwards - bool closeClipElement = false; - Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; - if (clipConfig) { - closeClipElement = true; - for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { - Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); - if (mapping->layoutElement == currentElement) { - scrollOffset = clipConfig->childOffset; - if (context->externalScrollHandlingEnabled) { - scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; - } - break; } } - } - if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_BORDER)) { - Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); - Clay_BoundingBox currentElementBoundingBox = currentElementData->boundingBox; - - // Culling - Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow - if (!Clay__ElementIsOffscreen(¤tElementBoundingBox)) { - Clay_SharedElementConfig *sharedConfig = Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SHARED) ? Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SHARED).sharedElementConfig : &Clay_SharedElementConfig_DEFAULT; - Clay_BorderElementConfig *borderConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_BORDER).borderElementConfig; + if (Clay__BorderHasAnyWidth(¤tElement->config.border)) { + Clay_BoundingBox currentElementBoundingBox = currentElementData->boundingBox; + Clay_BorderElementConfig *borderConfig = ¤tElement->config.border; Clay_RenderCommand renderCommand = { - .boundingBox = currentElementBoundingBox, - .renderData = { .border = { - .color = borderConfig->color, - .cornerRadius = sharedConfig->cornerRadius, - .width = borderConfig->width - }}, - .userData = sharedConfig->userData, - .id = Clay__HashNumber(currentElement->id, currentElement->childrenOrTextContent.children.length).id, - .commandType = CLAY_RENDER_COMMAND_TYPE_BORDER, + .boundingBox = currentElementBoundingBox, + .renderData = { .border = { + .color = borderConfig->color, + .cornerRadius = currentElement->config.cornerRadius, + .width = borderConfig->width + }}, + .userData = currentElement->config.userData, + .id = Clay__HashNumber(currentElement->id, currentElement->children.length).id, + .commandType = CLAY_RENDER_COMMAND_TYPE_BORDER, }; Clay__AddRenderCommand(renderCommand); if (borderConfig->width.betweenChildren > 0 && borderConfig->color.a > 0) { float halfGap = layoutConfig->childGap / 2; + float halfWidth = borderConfig->width.betweenChildren / 2; Clay_Vector2 borderOffset = { (float)layoutConfig->padding.left - halfGap, (float)layoutConfig->padding.top - halfGap }; if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { - for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { - Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + for (int32_t i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); if (i > 0) { Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { - .boundingBox = { currentElementBoundingBox.x + borderOffset.x + scrollOffset.x, currentElementBoundingBox.y + scrollOffset.y, (float)borderConfig->width.betweenChildren, currentElement->dimensions.height }, - .renderData = { .rectangle = { - .backgroundColor = borderConfig->color, - } }, - .userData = sharedConfig->userData, - .id = Clay__HashNumber(currentElement->id, currentElement->childrenOrTextContent.children.length + 1 + i).id, - .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + .boundingBox = { currentElementBoundingBox.x + borderOffset.x + scrollOffset.x - halfWidth, currentElementBoundingBox.y + scrollOffset.y, (float)borderConfig->width.betweenChildren, currentElement->dimensions.height }, + .renderData = { .rectangle = { + .backgroundColor = borderConfig->color, + } }, + .userData = currentElement->config.userData, + .id = Clay__HashNumber(currentElement->id, currentElement->children.length + 1 + i).id, + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, }); } borderOffset.x += (childElement->dimensions.width + (float)layoutConfig->childGap); } } else { - for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { - Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + for (int32_t i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); if (i > 0) { Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { - .boundingBox = { currentElementBoundingBox.x + scrollOffset.x, currentElementBoundingBox.y + borderOffset.y + scrollOffset.y, currentElement->dimensions.width, (float)borderConfig->width.betweenChildren }, - .renderData = { .rectangle = { - .backgroundColor = borderConfig->color, - } }, - .userData = sharedConfig->userData, - .id = Clay__HashNumber(currentElement->id, currentElement->childrenOrTextContent.children.length + 1 + i).id, - .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + .boundingBox = { currentElementBoundingBox.x + scrollOffset.x, currentElementBoundingBox.y + borderOffset.y + scrollOffset.y - halfWidth, currentElement->dimensions.width, (float)borderConfig->width.betweenChildren }, + .renderData = { .rectangle = { + .backgroundColor = borderConfig->color, + } }, + .userData = currentElement->config.userData, + .id = Clay__HashNumber(currentElement->id, currentElement->children.length + 1 + i).id, + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, }); } borderOffset.y += (childElement->dimensions.height + (float)layoutConfig->childGap); @@ -3072,58 +2860,280 @@ void Clay__CalculateFinalLayout(void) { } } } - } - // This exists because the scissor needs to end _after_ borders between elements - if (closeClipElement) { - Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { - .id = Clay__HashNumber(currentElement->id, rootElement->childrenOrTextContent.children.length + 11).id, - .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, - }); + if (currentElement->config.overlayColor.a > 0) { + Clay_RenderCommand renderCommand = { + .userData = currentElement->config.userData, + .id = currentElement->id, + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_END, + }; + Clay__AddRenderCommand(renderCommand); + } + // This exists because the scissor needs to end _after_ borders between elements + if (closeClipElement) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .id = Clay__HashNumber(currentElement->id, rootElement->children.length + 11).id, + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, + }); + } } dfsBuffer.length--; continue; } - // Add children to the DFS buffer - if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { - dfsBuffer.length += currentElement->childrenOrTextContent.children.length; - for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { - Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); - // Alignment along non layout axis - if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { - currentElementTreeNode->nextChildOffset.y = currentElement->layoutConfig->padding.top; - float whiteSpaceAroundChild = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - childElement->dimensions.height; - switch (layoutConfig->childAlignment.y) { - case CLAY_ALIGN_Y_TOP: break; - case CLAY_ALIGN_Y_CENTER: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild / 2; break; - case CLAY_ALIGN_Y_BOTTOM: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild; break; + // This will only be run a single time for each element in downwards DFS order + context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + Clay_BoundingBox currentElementBoundingBox = { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height }; + if (useStoredBoundingBoxes && currentElement->config.transition.handler) { + for (int j = 0; j < context->transitionDatas.length; ++j) { + Clay__TransitionDataInternal* transitionData = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, j); + if (transitionData->elementId == currentElement->id && transitionData->state != CLAY_TRANSITION_STATE_IDLE) { + if ((currentElement->config.transition.properties & CLAY_TRANSITION_PROPERTY_X) != 0) currentElementBoundingBox.x = transitionData->currentState.boundingBox.x; + if ((currentElement->config.transition.properties & CLAY_TRANSITION_PROPERTY_Y) != 0) currentElementBoundingBox.y = transitionData->currentState.boundingBox.y; + if ((currentElement->config.transition.properties & CLAY_TRANSITION_PROPERTY_WIDTH) != 0) currentElementBoundingBox.width = transitionData->currentState.boundingBox.width; + if ((currentElement->config.transition.properties & CLAY_TRANSITION_PROPERTY_HEIGHT) != 0) currentElementBoundingBox.height = transitionData->currentState.boundingBox.height; + } + } + } + if (currentElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { + Clay_FloatingElementConfig *floatingElementConfig = ¤tElement->config.floating; + Clay_Dimensions expand = floatingElementConfig->expand; + currentElementBoundingBox.x -= expand.width; + currentElementBoundingBox.width += expand.width * 2; + currentElementBoundingBox.y -= expand.height; + currentElementBoundingBox.height += expand.height * 2; + } + + Clay__ScrollContainerDataInternal *scrollContainerData = CLAY__NULL; + // Apply scroll offsets to container + if (currentElement->config.clip.horizontal || currentElement->config.clip.vertical) { + // This linear scan could theoretically be slow under very strange conditions, but I can't imagine a real UI with more than a few 10's of scroll containers + for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); + if (mapping->layoutElement == currentElement) { + scrollContainerData = mapping; + mapping->boundingBox = currentElementBoundingBox; + scrollOffset = currentElement->config.clip.childOffset; + if (context->externalScrollHandlingEnabled) { + scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; } - } else { - currentElementTreeNode->nextChildOffset.x = currentElement->layoutConfig->padding.left; - float whiteSpaceAroundChild = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - childElement->dimensions.width; - switch (layoutConfig->childAlignment.x) { - case CLAY_ALIGN_X_LEFT: break; - case CLAY_ALIGN_X_CENTER: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild / 2; break; - case CLAY_ALIGN_X_RIGHT: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild; break; + break; + } + } + } + + bool offscreen = Clay__ElementIsOffscreen(¤tElementBoundingBox); + + // Generate render commands for current element + if (generateRenderCommands && !offscreen) { + if (currentElement->isTextElement) { + Clay_TextElementConfig *textElementConfig = ¤tElement->textConfig; + float naturalLineHeight = currentElement->textElementData.preferredDimensions.height; + float finalLineHeight = textElementConfig->lineHeight > 0 ? (float)textElementConfig->lineHeight : naturalLineHeight; + float lineHeightOffset = (finalLineHeight - naturalLineHeight) / 2; + float yPosition = lineHeightOffset; + for (int32_t lineIndex = 0; lineIndex < currentElement->textElementData.wrappedLines.length; ++lineIndex) { + Clay__WrappedTextLine *wrappedLine = Clay__WrappedTextLineArraySlice_Get(¤tElement->textElementData.wrappedLines, lineIndex); + if (wrappedLine->line.length == 0) { + yPosition += finalLineHeight; + continue; + } + float offset = (currentElementBoundingBox.width - wrappedLine->dimensions.width); + if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_LEFT) { + offset = 0; + } + if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { + offset /= 2; + } + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = { currentElementBoundingBox.x + offset, currentElementBoundingBox.y + yPosition, wrappedLine->dimensions.width, wrappedLine->dimensions.height }, + .renderData = { .text = { + .stringContents = CLAY__INIT(Clay_StringSlice) { .length = wrappedLine->line.length, .chars = wrappedLine->line.chars, .baseChars = currentElement->textElementData.text.chars }, + .textColor = textElementConfig->textColor, + .fontId = textElementConfig->fontId, + .fontSize = textElementConfig->fontSize, + .letterSpacing = textElementConfig->letterSpacing, + .lineHeight = textElementConfig->lineHeight, + }}, + .userData = textElementConfig->userData, + .id = Clay__HashNumber(lineIndex, currentElement->id).id, + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT, + }); + yPosition += finalLineHeight; + + if (!context->disableCulling && (currentElementBoundingBox.y + yPosition > context->layoutDimensions.height)) { + break; } } + } else { + if (currentElement->config.overlayColor.a > 0) { + Clay_RenderCommand renderCommand = { + .renderData = { + .overlayColor = { .color = currentElement->config.overlayColor } + }, + .userData = currentElement->config.userData, + .id = currentElement->id, + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START, + }; + Clay__AddRenderCommand(renderCommand); + } + if (currentElement->config.image.imageData) { + Clay_RenderCommand renderCommand = { + .boundingBox = currentElementBoundingBox, + .renderData = { + .image = { + .backgroundColor = currentElement->config.backgroundColor, + .cornerRadius = currentElement->config.cornerRadius, + .imageData = currentElement->config.image.imageData, + } + }, + .userData = currentElement->config.userData, + .id = currentElement->id, + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE, + }; + Clay__AddRenderCommand(renderCommand); + } + if (currentElement->config.custom.customData) { + Clay_RenderCommand renderCommand = { + .boundingBox = currentElementBoundingBox, + .renderData = { + .custom = { + .backgroundColor = currentElement->config.backgroundColor, + .cornerRadius = currentElement->config.cornerRadius, + .customData = currentElement->config.custom.customData, + } + }, + .userData = currentElement->config.userData, + .id = currentElement->id, + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_CUSTOM, + }; + Clay__AddRenderCommand(renderCommand); + } + if (currentElement->config.clip.horizontal || currentElement->config.clip.vertical) { + Clay_RenderCommand renderCommand = { + .boundingBox = currentElementBoundingBox, + .renderData = { + .clip = { + .horizontal = currentElement->config.clip.horizontal, + .vertical = currentElement->config.clip.vertical, + } + }, + .userData = currentElement->config.userData, + .id = currentElement->id, + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, + }; + Clay__AddRenderCommand(renderCommand); + } + if (currentElement->config.backgroundColor.a > 0) { + Clay_RenderCommand renderCommand = { + .boundingBox = currentElementBoundingBox, + .renderData = { .rectangle = { + .backgroundColor = currentElement->config.backgroundColor, + .cornerRadius = currentElement->config.cornerRadius, + } }, + .userData = currentElement->config.userData, + .id = currentElement->id, + .zIndex = root->zIndex, + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + }; + Clay__AddRenderCommand(renderCommand); + } + } + } - Clay_Vector2 childPosition = { - currentElementTreeNode->position.x + currentElementTreeNode->nextChildOffset.x + scrollOffset.x, - currentElementTreeNode->position.y + currentElementTreeNode->nextChildOffset.y + scrollOffset.y, - }; + Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(currentElement->id); + hashMapItem->boundingBox = currentElementBoundingBox; - // DFS buffer elements need to be added in reverse because stack traversal happens backwards - uint32_t newNodeIndex = dfsBuffer.length - 1 - i; - dfsBuffer.internalArray[newNodeIndex] = CLAY__INIT(Clay__LayoutElementTreeNode) { - .layoutElement = childElement, - .position = { childPosition.x, childPosition.y }, - .nextChildOffset = { .x = (float)childElement->layoutConfig->padding.left, .y = (float)childElement->layoutConfig->padding.top }, - }; - context->treeNodeVisited.internalArray[newNodeIndex] = false; + if (currentElement->isTextElement) continue; - // Update parent offsets + // Setup positions for child elements and add to DFS buffer ---------- + + // On-axis alignment + Clay_Dimensions contentSizeCurrent = {}; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + for (int32_t i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); + if (childElement->exiting) continue; + contentSizeCurrent.width += childElement->dimensions.width; + contentSizeCurrent.height = CLAY__MAX(contentSizeCurrent.height, childElement->dimensions.height); + } + contentSizeCurrent.width += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap); + float extraSpace = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - contentSizeCurrent.width; + switch (layoutConfig->childAlignment.x) { + case CLAY_ALIGN_X_LEFT: extraSpace = 0; break; + case CLAY_ALIGN_X_CENTER: extraSpace /= 2; break; + default: break; + } + extraSpace = CLAY__MAX(0, extraSpace); + currentElementTreeNode->nextChildOffset.x += extraSpace; + } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { + for (int32_t i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); + if (childElement->exiting) continue; + contentSizeCurrent.width = CLAY__MAX(contentSizeCurrent.width, childElement->dimensions.width); + contentSizeCurrent.height += childElement->dimensions.height; + } + contentSizeCurrent.height += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap); + float extraSpace = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - contentSizeCurrent.height; + switch (layoutConfig->childAlignment.y) { + case CLAY_ALIGN_Y_TOP: extraSpace = 0; break; + case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break; + default: break; + } + extraSpace = CLAY__MAX(0, extraSpace); + currentElementTreeNode->nextChildOffset.y += extraSpace; + } + + if (scrollContainerData) { + scrollContainerData->contentSize = CLAY__INIT(Clay_Dimensions) {contentSizeCurrent.width + (float)(layoutConfig->padding.left + layoutConfig->padding.right), contentSizeCurrent.height + (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) }; + } + + // Add children to the DFS buffer + dfsBuffer.length += currentElement->children.length; + for (int32_t i = 0; i < currentElement->children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); + Clay_LayoutElementHashMapItem* childMapItem = Clay__GetHashMapItem(childElement->id); + // Alignment along non layout axis + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + currentElementTreeNode->nextChildOffset.y = currentElement->config.layout.padding.top; + float whiteSpaceAroundChild = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - childElement->dimensions.height; + switch (layoutConfig->childAlignment.y) { + case CLAY_ALIGN_Y_TOP: break; + case CLAY_ALIGN_Y_CENTER: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild / 2; break; + case CLAY_ALIGN_Y_BOTTOM: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild; break; + } + } else { + currentElementTreeNode->nextChildOffset.x = currentElement->config.layout.padding.left; + float whiteSpaceAroundChild = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - childElement->dimensions.width; + switch (layoutConfig->childAlignment.x) { + case CLAY_ALIGN_X_LEFT: break; + case CLAY_ALIGN_X_CENTER: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild / 2; break; + case CLAY_ALIGN_X_RIGHT: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild; break; + } + } + + Clay_Vector2 childPosition = { + currentElementBoundingBox.x + currentElementTreeNode->nextChildOffset.x + scrollOffset.x, + currentElementBoundingBox.y + currentElementTreeNode->nextChildOffset.y + scrollOffset.y, + }; + + // DFS buffer elements need to be added in reverse because stack traversal happens backwards + uint32_t newNodeIndex = dfsBuffer.length - 1 - i; + dfsBuffer.internalArray[newNodeIndex] = CLAY__INIT(Clay__LayoutElementTreeNode) { + .layoutElement = childElement, + .position = CLAY__INIT(Clay_Vector2) { childPosition.x, childPosition.y }, + .nextChildOffset = { .x = (float)childElement->config.layout.padding.left, .y = (float)childElement->config.layout.padding.top }, + }; + context->treeNodeVisited.internalArray[newNodeIndex] = false; + + // Update parent offsets + if (!childElement->exiting) { if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { currentElementTreeNode->nextChildOffset.x += childElement->dimensions.width + (float)layoutConfig->childGap; } else { @@ -3134,7 +3144,10 @@ void Clay__CalculateFinalLayout(void) { } if (root->clipElementId) { - Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .id = Clay__HashNumber(rootElement->id, rootElement->childrenOrTextContent.children.length + 11).id, .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END }); + Clay_LayoutElementHashMapItem *clipHashMapItem = Clay__GetHashMapItem(root->clipElementId); + if (clipHashMapItem && !Clay__ElementIsOffscreen(&clipHashMapItem->boundingBox)) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .id = Clay__HashNumber(rootElement->id, rootElement->children.length + 11).id, .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END }); + } } } } @@ -3161,9 +3174,24 @@ typedef struct { Clay_Color color; } Clay__DebugElementConfigTypeLabelConfig; -Clay__DebugElementConfigTypeLabelConfig Clay__DebugGetElementConfigTypeLabel(Clay__ElementConfigType type) { +typedef enum { + CLAY__ELEMENT_CONFIG_TYPE_BACKGROUND_COLOR, + CLAY__ELEMENT_CONFIG_TYPE_OVERLAY_COLOR, + CLAY__ELEMENT_CONFIG_TYPE_CORNER_RADIUS, + CLAY__ELEMENT_CONFIG_TYPE_TEXT, + CLAY__ELEMENT_CONFIG_TYPE_ASPECT, + CLAY__ELEMENT_CONFIG_TYPE_IMAGE, + CLAY__ELEMENT_CONFIG_TYPE_FLOATING, + CLAY__ELEMENT_CONFIG_TYPE_CLIP, + CLAY__ELEMENT_CONFIG_TYPE_BORDER, + CLAY__ELEMENT_CONFIG_TYPE_CUSTOM, +} Clay__DebugElementConfigType; + +Clay__DebugElementConfigTypeLabelConfig Clay__DebugGetElementConfigTypeLabel(Clay__DebugElementConfigType type) { switch (type) { - case CLAY__ELEMENT_CONFIG_TYPE_SHARED: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Shared"), {243,134,48,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_BACKGROUND_COLOR: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Background"), {243,134,48,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_OVERLAY_COLOR: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Overlay"), { 142,129,206, 255} }; + case CLAY__ELEMENT_CONFIG_TYPE_CORNER_RADIUS: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) {CLAY_STRING("Radius"), {239,148,157, 255 } }; case CLAY__ELEMENT_CONFIG_TYPE_TEXT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Text"), {105,210,231,255} }; case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Aspect"), {101,149,194,255} }; case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Image"), {121,189,154,255} }; @@ -3176,6 +3204,14 @@ Clay__DebugElementConfigTypeLabelConfig Clay__DebugGetElementConfigTypeLabel(Cla return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Error"), {0,0,0,255} }; } +void Clay__RenderElementConfigTypeLabel(Clay_String label, Clay_Color color, bool offscreen) { + Clay_Color backgroundColor = color; + backgroundColor.a = 90; + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = color, .width = { 1, 1, 1, 1, 0 } } }) { + CLAY_TEXT(label, CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } +} + typedef struct { int32_t rowCount; int32_t selectedElementRowIndex; @@ -3205,7 +3241,7 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR int32_t currentElementIndex = Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1); Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)currentElementIndex); if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { - if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && currentElement->childrenOrTextContent.children.length > 0) { + if (!currentElement->isTextElement && currentElement->children.length > 0) { Clay__CloseElement(); Clay__CloseElement(); Clay__CloseElement(); @@ -3214,6 +3250,11 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR continue; } + if (currentElement->exiting) { // TODO there is a duplicate ID problem with exiting elements + dfsBuffer.length--; + continue; + } + if (highlightedRowIndex == layoutData.rowCount) { if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { context->debugSelectedElementId = currentElement->id; @@ -3229,7 +3270,7 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR } CLAY(CLAY_IDI("Clay__DebugView_ElementOuter", currentElement->id), { .layout = Clay__DebugView_ScrollViewItemLayoutConfig }) { // Collapse icon / button - if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0)) { + if (!(currentElement->isTextElement || currentElement->children.length == 0)) { CLAY(CLAY_IDI("Clay__DebugView_CollapseElement", currentElement->id), { .layout = { .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }, .cornerRadius = CLAY_CORNER_RADIUS(4), @@ -3255,43 +3296,64 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR } } } - Clay_String idString = context->layoutElementIdStrings.internalArray[currentElementIndex]; - if (idString.length > 0) { - CLAY_TEXT(idString, offscreen ? CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 }) : &Clay__DebugView_TextNameConfig); - } - for (int32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { - Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, elementConfigIndex); - if (elementConfig->type == CLAY__ELEMENT_CONFIG_TYPE_SHARED) { - Clay_Color labelColor = {243,134,48,90}; - labelColor.a = 90; - Clay_Color backgroundColor = elementConfig->config.sharedElementConfig->backgroundColor; - Clay_CornerRadius radius = elementConfig->config.sharedElementConfig->cornerRadius; - if (backgroundColor.a > 0) { - CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = labelColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = labelColor, .width = { 1, 1, 1, 1, 0} } }) { - CLAY_TEXT(CLAY_STRING("Color"), CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); - } + if (currentElementData->elementId.stringId.length > 0) { + CLAY_AUTO_ID() { + Clay_TextElementConfig textConfig = offscreen ? CLAY__INIT(Clay_TextElementConfig) { .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 } : Clay__DebugView_TextNameConfig; + CLAY_TEXT(currentElementData->elementId.stringId, textConfig); + if (currentElementData->elementId.offset != 0) { + CLAY_TEXT(CLAY_STRING(" ("), textConfig); + CLAY_TEXT(Clay__IntToString(currentElementData->elementId.offset), textConfig); + CLAY_TEXT(CLAY_STRING(")"), textConfig); } - if (radius.bottomLeft > 0) { - CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = labelColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = labelColor, .width = { 1, 1, 1, 1, 0 } } }) { - CLAY_TEXT(CLAY_STRING("Radius"), CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); - } - } - continue; } - Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(elementConfig->type); - Clay_Color backgroundColor = config.color; - backgroundColor.a = 90; - CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = config.color, .width = { 1, 1, 1, 1, 0 } } }) { - CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + if (currentElement->isTextElement) { + Clay__RenderElementConfigTypeLabel(CLAY_STRING("Text"), CLAY__INIT(Clay_Color) { 105,210,231,255 }, offscreen); + } else { + if (currentElement->config.backgroundColor.a > 0) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_BACKGROUND_COLOR); + Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); + } + if (currentElement->config.overlayColor.a > 0) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_OVERLAY_COLOR); + Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); + } + if (!Clay__MemCmp((const char*)¤tElement->config.cornerRadius, (const char*)&Clay__CornerRadius_DEFAULT, sizeof(Clay_CornerRadius))) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_CORNER_RADIUS); + Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); + } + if (currentElement->config.aspectRatio.aspectRatio != 0) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_ASPECT); + Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); + } + if (currentElement->config.image.imageData) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_IMAGE); + Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); + } + if (currentElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_FLOATING); + Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); + } + if (currentElement->config.clip.horizontal || currentElement->config.clip.vertical) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_CLIP); + Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); + } + if (Clay__BorderHasAnyWidth(¤tElement->config.border)) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_BORDER); + Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); + } + if (currentElement->config.custom.customData) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_CUSTOM); + Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); } } } // Render the text contents below the element as a non-interactive row - if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + if (currentElement->isTextElement) { layoutData.rowCount++; - Clay__TextElementData *textElementData = currentElement->childrenOrTextContent.textElementData; - Clay_TextElementConfig *rawTextConfig = offscreen ? CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 }) : &Clay__DebugView_TextNameConfig; + Clay__TextElementData *textElementData = ¤tElement->textElementData; + Clay_TextElementConfig rawTextConfig = offscreen ? CLAY__INIT(Clay_TextElementConfig) { .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 } : Clay__DebugView_TextNameConfig; CLAY_AUTO_ID({ .layout = { .sizing = { .height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } } }) { CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_INDENT_WIDTH + 16) } } }) {} CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); @@ -3301,7 +3363,7 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR } CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); } - } else if (currentElement->childrenOrTextContent.children.length > 0) { + } else if (currentElement->children.length > 0) { Clay__OpenElement(); Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .padding = { .left = 8 } } }); Clay__OpenElement(); @@ -3311,9 +3373,9 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR } layoutData.rowCount++; - if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (currentElementData && currentElementData->debugData->collapsed))) { - for (int32_t i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) { - Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]); + if (!(currentElement->isTextElement || (currentElementData && currentElementData->debugData->collapsed))) { + for (int32_t i = currentElement->children.length - 1; i >= 0; --i) { + Clay__int32_tArray_Add(&dfsBuffer, currentElement->children.elements[i]); context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked } } @@ -3340,7 +3402,7 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR return layoutData; } -void Clay__RenderDebugLayoutSizing(Clay_SizingAxis sizing, Clay_TextElementConfig *infoTextConfig) { +void Clay__RenderDebugLayoutSizing(Clay_SizingAxis sizing, Clay_TextElementConfig infoTextConfig) { Clay_String sizingLabel = CLAY_STRING("GROW"); if (sizing.type == CLAY__SIZING_TYPE_FIT) { sizingLabel = CLAY_STRING("FIT"); @@ -3371,20 +3433,16 @@ void Clay__RenderDebugLayoutSizing(Clay_SizingAxis sizing, Clay_TextElementConfi } } -void Clay__RenderDebugViewElementConfigHeader(Clay_String elementId, Clay__ElementConfigType type) { +void Clay__DebugViewRenderElementConfigHeader(Clay_String elementId, Clay__DebugElementConfigType type) { Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(type); Clay_Color backgroundColor = config.color; backgroundColor.a = 90; - CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(CLAY__DEBUGVIEW_OUTER_PADDING), .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } } }) { - CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = config.color, .width = { 1, 1, 1, 1, 0 } } }) { - CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); - } - CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} - CLAY_TEXT(elementId, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE })); + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = config.color, .width = { 1, 1, 1, 1, 0 } } }) { + CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); } } -void Clay__RenderDebugViewColor(Clay_Color color, Clay_TextElementConfig *textConfig) { +void Clay__RenderDebugViewColor(Clay_Color color, Clay_TextElementConfig textConfig) { CLAY_AUTO_ID({ .layout = { .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { CLAY_TEXT(CLAY_STRING("{ r: "), textConfig); CLAY_TEXT(Clay__IntToString(color.r), textConfig); @@ -3400,7 +3458,7 @@ void Clay__RenderDebugViewColor(Clay_Color color, Clay_TextElementConfig *textCo } } -void Clay__RenderDebugViewCornerRadius(Clay_CornerRadius cornerRadius, Clay_TextElementConfig *textConfig) { +void Clay__RenderDebugViewCornerRadius(Clay_CornerRadius cornerRadius, Clay_TextElementConfig textConfig) { CLAY_AUTO_ID({ .layout = { .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { CLAY_TEXT(CLAY_STRING("{ topLeft: "), textConfig); CLAY_TEXT(Clay__IntToString(cornerRadius.topLeft), textConfig); @@ -3437,8 +3495,8 @@ void Clay__RenderDebugView(void) { uint32_t initialRootsLength = context->layoutElementTreeRoots.length; uint32_t initialElementsLength = context->layoutElements.length; - Clay_TextElementConfig *infoTextConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); - Clay_TextElementConfig *infoTitleConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); + Clay_TextElementConfig infoTextConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); + Clay_TextElementConfig infoTitleConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); Clay_ElementId scrollId = Clay__HashString(CLAY_STRING("Clay__DebugViewOuterScrollPane"), 0); float scrollYOffset = 0; bool pointerInDebugView = context->pointerInfo.position.y < context->layoutDimensions.height - 300; @@ -3515,7 +3573,7 @@ void Clay__RenderDebugView(void) { .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .betweenChildren = 1 } } }) { CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT + 8)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { - CLAY_TEXT(CLAY_STRING("Layout Config"), infoTextConfig); + CLAY_TEXT(CLAY_STRING("Element Configuration"), infoTextConfig); CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} if (selectedItem->elementId.stringId.length != 0) { CLAY_TEXT(selectedItem->elementId.stringId, infoTitleConfig); @@ -3529,6 +3587,9 @@ void Clay__RenderDebugView(void) { Clay_Padding attributeConfigPadding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 8, 8}; // Clay_LayoutConfig debug info CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = { 200, 200, 200, 120 }, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = { 200, 200, 200, 255 }, .width = { 1, 1, 1, 1, 0 } } }) { + CLAY_TEXT(CLAY_STRING("Layout"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } // .boundingBox CLAY_TEXT(CLAY_STRING("Bounding Box"), infoTitleConfig); CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { @@ -3544,7 +3605,7 @@ void Clay__RenderDebugView(void) { } // .layoutDirection CLAY_TEXT(CLAY_STRING("Layout Direction"), infoTitleConfig); - Clay_LayoutConfig *layoutConfig = selectedItem->layoutElement->layoutConfig; + Clay_LayoutConfig *layoutConfig = &selectedItem->layoutElement->config.layout; CLAY_TEXT(layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM ? CLAY_STRING("TOP_TO_BOTTOM") : CLAY_STRING("LEFT_TO_RIGHT"), infoTextConfig); // .sizing CLAY_TEXT(CLAY_STRING("Sizing"), infoTitleConfig); @@ -3594,234 +3655,238 @@ void Clay__RenderDebugView(void) { CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); } } - for (int32_t elementConfigIndex = 0; elementConfigIndex < selectedItem->layoutElement->elementConfigs.length; ++elementConfigIndex) { - Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(&selectedItem->layoutElement->elementConfigs, elementConfigIndex); - Clay__RenderDebugViewElementConfigHeader(selectedItem->elementId.stringId, elementConfig->type); - switch (elementConfig->type) { - case CLAY__ELEMENT_CONFIG_TYPE_SHARED: { - Clay_SharedElementConfig *sharedConfig = elementConfig->config.sharedElementConfig; - CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) { - // .backgroundColor - CLAY_TEXT(CLAY_STRING("Background Color"), infoTitleConfig); - Clay__RenderDebugViewColor(sharedConfig->backgroundColor, infoTextConfig); - // .cornerRadius - CLAY_TEXT(CLAY_STRING("Corner Radius"), infoTitleConfig); - Clay__RenderDebugViewCornerRadius(sharedConfig->cornerRadius, infoTextConfig); - } - break; + if (selectedItem->layoutElement->isTextElement) { + Clay_TextElementConfig *textConfig = &selectedItem->layoutElement->textConfig; + CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_TEXT); + // .fontSize + CLAY_TEXT(CLAY_STRING("Font Size"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(textConfig->fontSize), infoTextConfig); + // .fontId + CLAY_TEXT(CLAY_STRING("Font ID"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(textConfig->fontId), infoTextConfig); + // .lineHeight + CLAY_TEXT(CLAY_STRING("Line Height"), infoTitleConfig); + CLAY_TEXT(textConfig->lineHeight == 0 ? CLAY_STRING("auto") : Clay__IntToString(textConfig->lineHeight), infoTextConfig); + // .letterSpacing + CLAY_TEXT(CLAY_STRING("Letter Spacing"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(textConfig->letterSpacing), infoTextConfig); + // .wrapMode + CLAY_TEXT(CLAY_STRING("Wrap Mode"), infoTitleConfig); + Clay_String wrapMode = CLAY_STRING("WORDS"); + if (textConfig->wrapMode == CLAY_TEXT_WRAP_NONE) { + wrapMode = CLAY_STRING("NONE"); + } else if (textConfig->wrapMode == CLAY_TEXT_WRAP_NEWLINES) { + wrapMode = CLAY_STRING("NEWLINES"); } - case CLAY__ELEMENT_CONFIG_TYPE_TEXT: { - Clay_TextElementConfig *textConfig = elementConfig->config.textElementConfig; - CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { - // .fontSize - CLAY_TEXT(CLAY_STRING("Font Size"), infoTitleConfig); - CLAY_TEXT(Clay__IntToString(textConfig->fontSize), infoTextConfig); - // .fontId - CLAY_TEXT(CLAY_STRING("Font ID"), infoTitleConfig); - CLAY_TEXT(Clay__IntToString(textConfig->fontId), infoTextConfig); - // .lineHeight - CLAY_TEXT(CLAY_STRING("Line Height"), infoTitleConfig); - CLAY_TEXT(textConfig->lineHeight == 0 ? CLAY_STRING("auto") : Clay__IntToString(textConfig->lineHeight), infoTextConfig); - // .letterSpacing - CLAY_TEXT(CLAY_STRING("Letter Spacing"), infoTitleConfig); - CLAY_TEXT(Clay__IntToString(textConfig->letterSpacing), infoTextConfig); - // .wrapMode - CLAY_TEXT(CLAY_STRING("Wrap Mode"), infoTitleConfig); - Clay_String wrapMode = CLAY_STRING("WORDS"); - if (textConfig->wrapMode == CLAY_TEXT_WRAP_NONE) { - wrapMode = CLAY_STRING("NONE"); - } else if (textConfig->wrapMode == CLAY_TEXT_WRAP_NEWLINES) { - wrapMode = CLAY_STRING("NEWLINES"); - } - CLAY_TEXT(wrapMode, infoTextConfig); - // .textAlignment - CLAY_TEXT(CLAY_STRING("Text Alignment"), infoTitleConfig); - Clay_String textAlignment = CLAY_STRING("LEFT"); - if (textConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { - textAlignment = CLAY_STRING("CENTER"); - } else if (textConfig->textAlignment == CLAY_TEXT_ALIGN_RIGHT) { - textAlignment = CLAY_STRING("RIGHT"); - } - CLAY_TEXT(textAlignment, infoTextConfig); - // .textColor - CLAY_TEXT(CLAY_STRING("Text Color"), infoTitleConfig); - Clay__RenderDebugViewColor(textConfig->textColor, infoTextConfig); - } - break; + CLAY_TEXT(wrapMode, infoTextConfig); + // .textAlignment + CLAY_TEXT(CLAY_STRING("Text Alignment"), infoTitleConfig); + Clay_String textAlignment = CLAY_STRING("LEFT"); + if (textConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { + textAlignment = CLAY_STRING("CENTER"); + } else if (textConfig->textAlignment == CLAY_TEXT_ALIGN_RIGHT) { + textAlignment = CLAY_STRING("RIGHT"); } - case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: { - Clay_AspectRatioElementConfig *aspectRatioConfig = elementConfig->config.aspectRatioElementConfig; - CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatioBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { - CLAY_TEXT(CLAY_STRING("Aspect Ratio"), infoTitleConfig); - // Aspect Ratio - CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatio"), { }) { - CLAY_TEXT(Clay__IntToString(aspectRatioConfig->aspectRatio), infoTextConfig); - CLAY_TEXT(CLAY_STRING("."), infoTextConfig); - float frac = aspectRatioConfig->aspectRatio - (int)(aspectRatioConfig->aspectRatio); - frac *= 100; - if ((int)frac < 10) { - CLAY_TEXT(CLAY_STRING("0"), infoTextConfig); - } - CLAY_TEXT(Clay__IntToString(frac), infoTextConfig); - } - } - break; + CLAY_TEXT(textAlignment, infoTextConfig); + // .textColor + CLAY_TEXT(CLAY_STRING("Text Color"), infoTitleConfig); + Clay__RenderDebugViewColor(textConfig->textColor, infoTextConfig); + } + } else { + CLAY(CLAY_ID("Clay__DebugViewElementInfoSharedBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + Clay__DebugElementConfigTypeLabelConfig labelConfig = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_BACKGROUND_COLOR); + Clay_Color backgroundColor = labelConfig.color; + backgroundColor.a = 90; + CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .backgroundColor = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = labelConfig.color, .width = { 1, 1, 1, 1, 0 } } }) { + CLAY_TEXT(CLAY_STRING("Color & Radius"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); } - case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { - Clay_ImageElementConfig *imageConfig = elementConfig->config.imageElementConfig; - Clay_AspectRatioElementConfig aspectConfig = { 1 }; - if (Clay__ElementHasConfig(selectedItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT)) { - aspectConfig = *Clay__FindElementConfigWithType(selectedItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; - } - CLAY(CLAY_ID("Clay__DebugViewElementInfoImageBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { - // Image Preview - CLAY_TEXT(CLAY_STRING("Preview"), infoTitleConfig); - CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(64, 128), .height = CLAY_SIZING_GROW(64, 128) }}, .aspectRatio = aspectConfig, .image = *imageConfig }) {} - } - break; + // .backgroundColor + if (selectedItem->layoutElement->config.backgroundColor.a > 0) { + CLAY_TEXT(CLAY_STRING("Background Color"), infoTitleConfig); + Clay__RenderDebugViewColor(selectedItem->layoutElement->config.backgroundColor, infoTextConfig); } - case CLAY__ELEMENT_CONFIG_TYPE_CLIP: { - Clay_ClipElementConfig *clipConfig = elementConfig->config.clipElementConfig; - CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { - // .vertical - CLAY_TEXT(CLAY_STRING("Vertical"), infoTitleConfig); - CLAY_TEXT(clipConfig->vertical ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); - // .horizontal - CLAY_TEXT(CLAY_STRING("Horizontal"), infoTitleConfig); - CLAY_TEXT(clipConfig->horizontal ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); - } - break; + // .cornerRadius + if (!Clay__MemCmp((const char*)&selectedItem->layoutElement->config.cornerRadius, (const char*)&Clay__CornerRadius_DEFAULT, sizeof(Clay_CornerRadius))) { + CLAY_TEXT(CLAY_STRING("Corner Radius"), infoTitleConfig); + Clay__RenderDebugViewCornerRadius(selectedItem->layoutElement->config.cornerRadius, infoTextConfig); } - case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: { - Clay_FloatingElementConfig *floatingConfig = elementConfig->config.floatingElementConfig; - CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { - // .offset - CLAY_TEXT(CLAY_STRING("Offset"), infoTitleConfig); - CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { - CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(floatingConfig->offset.x), infoTextConfig); - CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(floatingConfig->offset.y), infoTextConfig); - CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); - } - // .expand - CLAY_TEXT(CLAY_STRING("Expand"), infoTitleConfig); - CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { - CLAY_TEXT(CLAY_STRING("{ width: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(floatingConfig->expand.width), infoTextConfig); - CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(floatingConfig->expand.height), infoTextConfig); - CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); - } - // .zIndex - CLAY_TEXT(CLAY_STRING("z-index"), infoTitleConfig); - CLAY_TEXT(Clay__IntToString(floatingConfig->zIndex), infoTextConfig); - // .parentId - CLAY_TEXT(CLAY_STRING("Parent"), infoTitleConfig); - Clay_LayoutElementHashMapItem *hashItem = Clay__GetHashMapItem(floatingConfig->parentId); - CLAY_TEXT(hashItem->elementId.stringId, infoTextConfig); - // .attachPoints - CLAY_TEXT(CLAY_STRING("Attach Points"), infoTitleConfig); - CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { - CLAY_TEXT(CLAY_STRING("{ element: "), infoTextConfig); - Clay_String attachPointElement = CLAY_STRING("LEFT_TOP"); - if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_CENTER) { - attachPointElement = CLAY_STRING("LEFT_CENTER"); - } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_BOTTOM) { - attachPointElement = CLAY_STRING("LEFT_BOTTOM"); - } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_TOP) { - attachPointElement = CLAY_STRING("CENTER_TOP"); - } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_CENTER) { - attachPointElement = CLAY_STRING("CENTER_CENTER"); - } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_BOTTOM) { - attachPointElement = CLAY_STRING("CENTER_BOTTOM"); - } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_TOP) { - attachPointElement = CLAY_STRING("RIGHT_TOP"); - } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_CENTER) { - attachPointElement = CLAY_STRING("RIGHT_CENTER"); - } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { - attachPointElement = CLAY_STRING("RIGHT_BOTTOM"); - } - CLAY_TEXT(attachPointElement, infoTextConfig); - Clay_String attachPointParent = CLAY_STRING("LEFT_TOP"); - if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_CENTER) { - attachPointParent = CLAY_STRING("LEFT_CENTER"); - } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_BOTTOM) { - attachPointParent = CLAY_STRING("LEFT_BOTTOM"); - } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_TOP) { - attachPointParent = CLAY_STRING("CENTER_TOP"); - } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_CENTER) { - attachPointParent = CLAY_STRING("CENTER_CENTER"); - } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_BOTTOM) { - attachPointParent = CLAY_STRING("CENTER_BOTTOM"); - } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_TOP) { - attachPointParent = CLAY_STRING("RIGHT_TOP"); - } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_CENTER) { - attachPointParent = CLAY_STRING("RIGHT_CENTER"); - } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { - attachPointParent = CLAY_STRING("RIGHT_BOTTOM"); - } - CLAY_TEXT(CLAY_STRING(", parent: "), infoTextConfig); - CLAY_TEXT(attachPointParent, infoTextConfig); - CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); - } - // .pointerCaptureMode - CLAY_TEXT(CLAY_STRING("Pointer Capture Mode"), infoTitleConfig); - Clay_String pointerCaptureMode = CLAY_STRING("NONE"); - if (floatingConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH) { - pointerCaptureMode = CLAY_STRING("PASSTHROUGH"); - } - CLAY_TEXT(pointerCaptureMode, infoTextConfig); - // .attachTo - CLAY_TEXT(CLAY_STRING("Attach To"), infoTitleConfig); - Clay_String attachTo = CLAY_STRING("NONE"); - if (floatingConfig->attachTo == CLAY_ATTACH_TO_PARENT) { - attachTo = CLAY_STRING("PARENT"); - } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) { - attachTo = CLAY_STRING("ELEMENT_WITH_ID"); - } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ROOT) { - attachTo = CLAY_STRING("ROOT"); - } - CLAY_TEXT(attachTo, infoTextConfig); - // .clipTo - CLAY_TEXT(CLAY_STRING("Clip To"), infoTitleConfig); - Clay_String clipTo = CLAY_STRING("ATTACHED_PARENT"); - if (floatingConfig->clipTo == CLAY_CLIP_TO_NONE) { - clipTo = CLAY_STRING("NONE"); - } - CLAY_TEXT(clipTo, infoTextConfig); - } - break; + // .overlayColor + if (selectedItem->layoutElement->config.overlayColor.a > 0) { + CLAY_TEXT(CLAY_STRING("Overlay Color"), infoTitleConfig); + Clay__RenderDebugViewColor(selectedItem->layoutElement->config.overlayColor, infoTextConfig); } - case CLAY__ELEMENT_CONFIG_TYPE_BORDER: { - Clay_BorderElementConfig *borderConfig = elementConfig->config.borderElementConfig; - CLAY(CLAY_ID("Clay__DebugViewElementInfoBorderBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { - CLAY_TEXT(CLAY_STRING("Border Widths"), infoTitleConfig); - CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { - CLAY_TEXT(CLAY_STRING("{ left: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(borderConfig->width.left), infoTextConfig); - CLAY_TEXT(CLAY_STRING(", right: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(borderConfig->width.right), infoTextConfig); - CLAY_TEXT(CLAY_STRING(", top: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(borderConfig->width.top), infoTextConfig); - CLAY_TEXT(CLAY_STRING(", bottom: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(borderConfig->width.bottom), infoTextConfig); - CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + if (selectedItem->layoutElement->config.aspectRatio.aspectRatio > 0) { + Clay_AspectRatioElementConfig *aspectRatioConfig = &selectedItem->layoutElement->config.aspectRatio; + CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatioBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_ASPECT); + CLAY_TEXT(CLAY_STRING("Aspect Ratio"), infoTitleConfig); + // Aspect Ratio + CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatio"), { }) { + CLAY_TEXT(Clay__IntToString(aspectRatioConfig->aspectRatio), infoTextConfig); + CLAY_TEXT(CLAY_STRING("."), infoTextConfig); + float frac = aspectRatioConfig->aspectRatio - (int)(aspectRatioConfig->aspectRatio); + frac *= 100; + if ((int)frac < 10) { + CLAY_TEXT(CLAY_STRING("0"), infoTextConfig); } - // .textColor - CLAY_TEXT(CLAY_STRING("Border Color"), infoTitleConfig); - Clay__RenderDebugViewColor(borderConfig->color, infoTextConfig); + CLAY_TEXT(Clay__IntToString(frac), infoTextConfig); } - break; } - case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: - default: break; + } + if (selectedItem->layoutElement->config.image.imageData) { + Clay_ImageElementConfig *imageConfig = &selectedItem->layoutElement->config.image; + Clay_AspectRatioElementConfig aspectConfig = { 1 }; + if (selectedItem->layoutElement->config.aspectRatio.aspectRatio > 0) { + aspectConfig = selectedItem->layoutElement->config.aspectRatio; + } + CLAY(CLAY_ID("Clay__DebugViewElementInfoImageBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_IMAGE); + // Image Preview + CLAY_TEXT(CLAY_STRING("Preview"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(64, 128), .height = CLAY_SIZING_GROW(64, 128) }}, .aspectRatio = aspectConfig, .image = *imageConfig }) {} + } + } + if (selectedItem->layoutElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { + Clay_FloatingElementConfig* floatingConfig = &selectedItem->layoutElement->config.floating; + CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_FLOATING); + // .offset + CLAY_TEXT(CLAY_STRING("Offset"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->offset.x), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->offset.y), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .expand + CLAY_TEXT(CLAY_STRING("Expand"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ width: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->expand.width), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->expand.height), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .zIndex + CLAY_TEXT(CLAY_STRING("z-index"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->zIndex), infoTextConfig); + // .parentId + CLAY_TEXT(CLAY_STRING("Parent"), infoTitleConfig); + Clay_LayoutElementHashMapItem *hashItem = Clay__GetHashMapItem(floatingConfig->parentId); + CLAY_TEXT(hashItem->elementId.stringId, infoTextConfig); + // .attachPoints + CLAY_TEXT(CLAY_STRING("Attach Points"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ element: "), infoTextConfig); + Clay_String attachPointElement = CLAY_STRING("LEFT_TOP"); + if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_CENTER) { + attachPointElement = CLAY_STRING("LEFT_CENTER"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_BOTTOM) { + attachPointElement = CLAY_STRING("LEFT_BOTTOM"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_TOP) { + attachPointElement = CLAY_STRING("CENTER_TOP"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_CENTER) { + attachPointElement = CLAY_STRING("CENTER_CENTER"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_BOTTOM) { + attachPointElement = CLAY_STRING("CENTER_BOTTOM"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_TOP) { + attachPointElement = CLAY_STRING("RIGHT_TOP"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_CENTER) { + attachPointElement = CLAY_STRING("RIGHT_CENTER"); + } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { + attachPointElement = CLAY_STRING("RIGHT_BOTTOM"); + } + CLAY_TEXT(attachPointElement, infoTextConfig); + Clay_String attachPointParent = CLAY_STRING("LEFT_TOP"); + if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_CENTER) { + attachPointParent = CLAY_STRING("LEFT_CENTER"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_BOTTOM) { + attachPointParent = CLAY_STRING("LEFT_BOTTOM"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_TOP) { + attachPointParent = CLAY_STRING("CENTER_TOP"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_CENTER) { + attachPointParent = CLAY_STRING("CENTER_CENTER"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_BOTTOM) { + attachPointParent = CLAY_STRING("CENTER_BOTTOM"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_TOP) { + attachPointParent = CLAY_STRING("RIGHT_TOP"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_CENTER) { + attachPointParent = CLAY_STRING("RIGHT_CENTER"); + } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { + attachPointParent = CLAY_STRING("RIGHT_BOTTOM"); + } + CLAY_TEXT(CLAY_STRING(", parent: "), infoTextConfig); + CLAY_TEXT(attachPointParent, infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .pointerCaptureMode + CLAY_TEXT(CLAY_STRING("Pointer Capture Mode"), infoTitleConfig); + Clay_String pointerCaptureMode = CLAY_STRING("NONE"); + if (floatingConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH) { + pointerCaptureMode = CLAY_STRING("PASSTHROUGH"); + } + CLAY_TEXT(pointerCaptureMode, infoTextConfig); + // .attachTo + CLAY_TEXT(CLAY_STRING("Attach To"), infoTitleConfig); + Clay_String attachTo = CLAY_STRING("NONE"); + if (floatingConfig->attachTo == CLAY_ATTACH_TO_PARENT) { + attachTo = CLAY_STRING("PARENT"); + } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) { + attachTo = CLAY_STRING("ELEMENT_WITH_ID"); + } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ROOT) { + attachTo = CLAY_STRING("ROOT"); + } + CLAY_TEXT(attachTo, infoTextConfig); + // .clipTo + CLAY_TEXT(CLAY_STRING("Clip To"), infoTitleConfig); + Clay_String clipTo = CLAY_STRING("ATTACHED_PARENT"); + if (floatingConfig->clipTo == CLAY_CLIP_TO_NONE) { + clipTo = CLAY_STRING("NONE"); + } + CLAY_TEXT(clipTo, infoTextConfig); + } + } + Clay_ClipElementConfig *clipConfig = &selectedItem->layoutElement->config.clip; + if (clipConfig->horizontal || clipConfig->vertical) { + CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_CLIP); + // .vertical + CLAY_TEXT(CLAY_STRING("Vertical"), infoTitleConfig); + CLAY_TEXT(clipConfig->vertical ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); + // .horizontal + CLAY_TEXT(CLAY_STRING("Horizontal"), infoTitleConfig); + CLAY_TEXT(clipConfig->horizontal ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); + } + } + Clay_BorderElementConfig *borderConfig = &selectedItem->layoutElement->config.border; + if (Clay__BorderHasAnyWidth(borderConfig)) { + CLAY(CLAY_ID("Clay__DebugViewElementInfoBorderBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_BORDER); + CLAY_TEXT(CLAY_STRING("Border Widths"), infoTitleConfig); + CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { + CLAY_TEXT(CLAY_STRING("{ left: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(borderConfig->width.left), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", right: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(borderConfig->width.right), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", top: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(borderConfig->width.top), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", bottom: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(borderConfig->width.bottom), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .textColor + CLAY_TEXT(CLAY_STRING("Border Color"), infoTitleConfig); + Clay__RenderDebugViewColor(borderConfig->color, infoTextConfig); + } } } } } else { CLAY(CLAY_ID("Clay__DebugViewWarningsScrollPane"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(300)}, .childGap = 6, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_2, .clip = { .horizontal = true, .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { - Clay_TextElementConfig *warningConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); + Clay_TextElementConfig warningConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); CLAY(CLAY_ID("Clay__DebugViewWarningItemHeader"), { .layout = { .sizing = {.height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childGap = 8, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { CLAY_TEXT(CLAY_STRING("Warnings"), warningConfig); } @@ -3962,6 +4027,8 @@ void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction) CLAY_WASM_EXPORT("Clay_SetLayoutDimensions") void Clay_SetLayoutDimensions(Clay_Dimensions dimensions) { + Clay_Context* context = Clay_GetCurrentContext(); + context->rootResizedLastFrame = !Clay__FloatEqual(context->layoutDimensions.width, dimensions.width) || !Clay__FloatEqual(context->layoutDimensions.height, dimensions.height); Clay_GetCurrentContext()->layoutDimensions = dimensions; } @@ -3980,6 +4047,7 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); context->treeNodeVisited.internalArray[0] = false; bool found = false; + bool skipTree = false; while (dfsBuffer.length > 0) { if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { dfsBuffer.length--; @@ -3987,26 +4055,46 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { } context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1)); + // Skip mouse interactions on an element if it's currently transitioning, based on user config + if (currentElement->config.transition.handler) { + for (int I = 0; I < context->transitionDatas.length; ++I) { + Clay__TransitionDataInternal* data = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, I); + if (data->elementId == currentElement->id) { + if (currentElement->config.transition.interactionHandling == CLAY_TRANSITION_DISABLE_INTERACTIONS_WHILE_TRANSITIONING_POSITION) { + if (data->state == CLAY_TRANSITION_STATE_EXITING || data->state == CLAY_TRANSITION_STATE_ENTERING || ((data->activeProperties & CLAY_TRANSITION_PROPERTY_POSITION) && data->state == CLAY_TRANSITION_STATE_TRANSITIONING)) { + skipTree = true; + } + } else if (currentElement->config.transition.interactionHandling == CLAY_TRANSITION_ALLOW_INTERACTIONS_WHILE_TRANSITIONING_POSITION) { + if (data->state == CLAY_TRANSITION_STATE_EXITING) { + skipTree = true; + } + } + } + } + } + Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO think of a way around this, maybe the fact that it's essentially a binary tree limits the cost, but the worst case is not great int32_t clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(currentElement - context->layoutElements.internalArray)); Clay_LayoutElementHashMapItem *clipItem = Clay__GetHashMapItem(clipElementId); - if (mapItem) { + if (mapItem && mapItem->generation > context->generation) { Clay_BoundingBox elementBox = mapItem->boundingBox; elementBox.x -= root->pointerOffset.x; elementBox.y -= root->pointerOffset.y; if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)) || context->externalScrollHandlingEnabled)) { - if (mapItem->onHoverFunction) { - mapItem->onHoverFunction(mapItem->elementId, context->pointerInfo, mapItem->hoverFunctionUserData); + if (!skipTree) { + if (mapItem->onHoverFunction) { + mapItem->onHoverFunction(mapItem->elementId, context->pointerInfo, mapItem->hoverFunctionUserData); + } + Clay_ElementIdArray_Add(&context->pointerOverIds, mapItem->elementId); } - Clay_ElementIdArray_Add(&context->pointerOverIds, mapItem->elementId); found = true; } - if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + if (skipTree || currentElement->isTextElement) { dfsBuffer.length--; continue; } - for (int32_t i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) { - Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]); + for (int32_t i = currentElement->children.length - 1; i >= 0; --i) { + Clay__int32_tArray_Add(&dfsBuffer, currentElement->children.elements[i]); context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked } } else { @@ -4015,8 +4103,7 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { } Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, root->layoutElementIndex); - if (found && Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING) && - Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_CAPTURE) { + if (found && rootElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE && rootElement->config.floating.pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_CAPTURE) { break; } } @@ -4036,6 +4123,11 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { } } +CLAY_WASM_EXPORT("Clay_GetPointerState") +CLAY_DLL_EXPORT Clay_PointerData Clay_GetPointerState(void) { + return Clay_GetCurrentContext()->pointerInfo; +} + CLAY_WASM_EXPORT("Clay_Initialize") Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler) { // Cacheline align memory passed in @@ -4084,10 +4176,6 @@ Clay_Vector2 Clay_GetScrollOffset(void) { return CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); - // If the element has no id attached at this point, we need to generate one - if (openLayoutElement->id == 0) { - Clay__GenerateIdForAnonymousElement(openLayoutElement); - } for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); if (mapping->layoutElement == openLayoutElement) { @@ -4161,7 +4249,7 @@ void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDe if (highestPriorityElementIndex > -1 && highestPriorityScrollData) { Clay_LayoutElement *scrollElement = highestPriorityScrollData->layoutElement; - Clay_ClipElementConfig *clipConfig = Clay__FindElementConfigWithType(scrollElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; + Clay_ClipElementConfig *clipConfig = &scrollElement->config.clip; bool canScrollVertically = clipConfig->vertical && highestPriorityScrollData->contentSize.height > scrollElement->dimensions.height; bool canScrollHorizontally = clipConfig->horizontal && highestPriorityScrollData->contentSize.width > scrollElement->dimensions.width; // Handle wheel scroll @@ -4231,28 +4319,345 @@ void Clay_BeginLayout(void) { Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { .layoutElementIndex = 0 }); } +void Clay__CloneElementsWithExitTransition() { + Clay_Context* context = Clay_GetCurrentContext(); + int32_t nextIndex = context->layoutElements.capacity - 1; + int32_t nextChildIndex = context->layoutElementChildren.capacity - 1; + + for (int i = 0; i < context->transitionDatas.length; ++i) { + Clay__TransitionDataInternal *data = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, i); + Clay_TransitionElementConfig* config = &data->elementThisFrame->config.transition; + if (data->transitionOut) { + Clay__int32_tArray bfsBuffer = context->openLayoutElementStack; + bfsBuffer.length = 0; + Clay_LayoutElement* newElement = Clay_LayoutElementArray_Set_DontTouchLength(&context->layoutElements, nextIndex, *data->elementThisFrame); + Clay__StringArray_Set_DontTouchLength(&context->layoutElementIdStrings, nextIndex, *Clay__StringArray_GetCheckCapacity(&context->layoutElementIdStrings, data->elementThisFrame - context->layoutElements.internalArray)); + Clay__int32_tArray_Add(&bfsBuffer, nextIndex); + data->elementThisFrame = newElement; + nextIndex--; + + int32_t bufferIndex = 0; + while(bufferIndex < bfsBuffer.length) { + Clay_LayoutElement *layoutElement = Clay_LayoutElementArray_GetCheckCapacity(&context->layoutElements, Clay__int32_tArray_GetValue(&bfsBuffer, bufferIndex)); + bufferIndex++; + for (int j = layoutElement->children.length - 1; j >= 0; --j) { + Clay_LayoutElement* childElement = Clay_LayoutElementArray_GetCheckCapacity(&context->layoutElements, layoutElement->children.elements[j]); + Clay__int32_tArray_Add(&bfsBuffer, nextIndex); + Clay_LayoutElement* newChildElement = Clay_LayoutElementArray_Set_DontTouchLength(&context->layoutElements, nextIndex, *childElement); + Clay__StringArray_Set_DontTouchLength(&context->layoutElementIdStrings, nextIndex, *Clay__StringArray_GetCheckCapacity(&context->layoutElementIdStrings, childElement - context->layoutElements.internalArray)); + Clay__int32_tArray_Set_DontTouchLength(&context->layoutElementChildren, nextChildIndex, nextIndex); + nextIndex--; + nextChildIndex--; + } + layoutElement->children.elements = &context->layoutElementChildren.internalArray[nextChildIndex + 1]; + } + } + } +}; + +void Clay_ApplyTransitionedPropertiesToElement(Clay_LayoutElement* currentElement, Clay_TransitionProperty properties, Clay_TransitionData currentTransitionData, Clay_BoundingBox* boundingBox, bool reparented) { + if (properties & CLAY_TRANSITION_PROPERTY_WIDTH) { + if (!reparented) { + currentElement->dimensions.width = currentTransitionData.boundingBox.width; + currentElement->config.layout.sizing.width = CLAY_SIZING_FIXED(currentTransitionData.boundingBox.width); + } else { + boundingBox->width = currentTransitionData.boundingBox.width; + } + } + if (properties & CLAY_TRANSITION_PROPERTY_HEIGHT) { + if (!reparented) { + currentElement->dimensions.height = currentTransitionData.boundingBox.height; + currentElement->config.layout.sizing.height = CLAY_SIZING_FIXED(currentTransitionData.boundingBox.height); + } else { + boundingBox->height = currentTransitionData.boundingBox.height; + } + } + if (properties & CLAY_TRANSITION_PROPERTY_X) { + boundingBox->x = currentTransitionData.boundingBox.x; + } + if (properties & CLAY_TRANSITION_PROPERTY_Y) { + boundingBox->y = currentTransitionData.boundingBox.y; + } + if (properties & CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR) { + currentElement->config.overlayColor = currentTransitionData.overlayColor; + } + if (properties & CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR) { + currentElement->config.backgroundColor = currentTransitionData.backgroundColor; + } + if (properties & CLAY_TRANSITION_PROPERTY_BORDER_COLOR) { + currentElement->config.border.color = currentTransitionData.borderColor; + } + if (properties & CLAY_TRANSITION_PROPERTY_BORDER_WIDTH) { + currentElement->config.border.width = currentTransitionData.borderWidth; + } +} + +void Clay__CreateDebugView() { + +} + CLAY_WASM_EXPORT("Clay_EndLayout") -Clay_RenderCommandArray Clay_EndLayout(void) { +Clay_RenderCommandArray Clay_EndLayout(float deltaTime) { Clay_Context* context = Clay_GetCurrentContext(); Clay__CloseElement(); - bool elementsExceededBeforeDebugView = context->booleanWarnings.maxElementsExceeded; - if (context->debugModeEnabled && !elementsExceededBeforeDebugView) { - context->warningsEnabled = false; - Clay__RenderDebugView(); - context->warningsEnabled = true; + + for (int i = 0; i < context->transitionDatas.length; ++i) { + Clay__TransitionDataInternal *data = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, i); + Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(data->elementId); + // This might seems strange - can't we just look up the element itself, and check the config to see whether it has an exit transition defined? + // That would work fine if the element actually had an exit transition in the first place. If it doesn't have an exit transition defined, the element + // will have simply disappeared completely at this point, and there will be no element through which to access the config. + if (data->transitionOut) { + Clay_TransitionElementConfig* config = &data->elementThisFrame->config.transition; + // Element wasn't found this frame - either delete transition data or transition out + if (hashMapItem->generation <= context->generation) { + Clay_LayoutElementHashMapItem *parentHashMapItem = Clay__GetHashMapItem(data->parentId); + // Don't exit transition if the parent has also exited and SKIP_WHEN_PARENT_EXITS is used + if (config->exit.trigger == CLAY_TRANSITION_EXIT_TRIGGER_WHEN_PARENT_EXITS || !parentHashMapItem || parentHashMapItem->generation > context->generation) { + if (data->state != CLAY_TRANSITION_STATE_EXITING) { + if (parentHashMapItem->generation <= context->generation) { + data->elementThisFrame->config.floating.attachTo = CLAY_ATTACH_TO_ROOT; + data->elementThisFrame->config.floating.offset = CLAY__INIT(Clay_Vector2) { hashMapItem->boundingBox.x, hashMapItem->boundingBox.y }; + data->siblingIndex = 0; + } + data->elementThisFrame->exiting = true; + data->elementThisFrame->config.layout.sizing.width = CLAY_SIZING_FIXED(data->elementThisFrame->dimensions.width); + data->elementThisFrame->config.layout.sizing.height = CLAY_SIZING_FIXED(data->elementThisFrame->dimensions.height); + data->state = CLAY_TRANSITION_STATE_EXITING; + data->activeProperties = config->properties; + data->elapsedTime = 0; + data->targetState = config->exit.setFinalState(data->targetState, config->properties); + } + + // Clone the entire subtree back into the main UI layout tree + Clay__int32_tArray bfsBuffer = context->openLayoutElementStack; + bfsBuffer.length = 0; + data->elementThisFrame = Clay_LayoutElementArray_Add(&context->layoutElements, *data->elementThisFrame); + int32_t exitingElementIndex = data->elementThisFrame - context->layoutElements.internalArray; + Clay__StringArray_Add(&context->layoutElementIdStrings, *Clay__StringArray_GetCheckCapacity(&context->layoutElementIdStrings, exitingElementIndex)); + Clay__int32_tArray_Add(&context->layoutElementClipElementIds, *Clay__int32_tArray_GetCheckCapacity(&context->layoutElementClipElementIds, exitingElementIndex)); + Clay__int32_tArray_Add(&bfsBuffer, exitingElementIndex); + hashMapItem->layoutElement = data->elementThisFrame; + hashMapItem->generation = context->generation + 1; + int32_t bufferIndex = 0; + while (bufferIndex < bfsBuffer.length) { + Clay_LayoutElement *layoutElement = Clay_LayoutElementArray_GetCheckCapacity(&context->layoutElements, Clay__int32_tArray_GetValue(&bfsBuffer, bufferIndex)); + bufferIndex++; + int32_t firstChildSlot = context->layoutElementChildren.length; + for (int j = 0; j < layoutElement->children.length; ++j) { + Clay_LayoutElement* childElement = Clay_LayoutElementArray_GetCheckCapacity(&context->layoutElements, layoutElement->children.elements[j]); + int32_t childElementIndex = childElement - context->layoutElements.internalArray; + Clay_LayoutElement* newChildElement = Clay_LayoutElementArray_Add(&context->layoutElements, *childElement); + Clay__StringArray_Add(&context->layoutElementIdStrings, *Clay__StringArray_GetCheckCapacity(&context->layoutElementIdStrings, childElementIndex)); + Clay__int32_tArray_Add(&context->layoutElementClipElementIds, *Clay__int32_tArray_GetCheckCapacity(&context->layoutElementClipElementIds, childElementIndex)); + Clay__int32_tArray_Add(&bfsBuffer, context->layoutElements.length - 1); + if (newChildElement->isTextElement) { + newChildElement->textElementData.wrappedLines.length = 0; + } + Clay__int32_tArray_Add(&context->layoutElementChildren, context->layoutElements.length - 1); + } + layoutElement->children.elements = &context->layoutElementChildren.internalArray[firstChildSlot]; + } + + // Reattach the inserted subtree to its previous parent if it still exists + if (parentHashMapItem->generation > context->generation) { + Clay_LayoutElement *parentElement = parentHashMapItem->layoutElement; + int32_t newChildrenStartIndex = context->layoutElementChildren.length; + bool found = false; + if (config->exit.siblingOrdering == CLAY_EXIT_TRANSITION_ORDERING_UNDERNEATH_SIBLINGS) { + Clay__int32_tArray_Add(&context->layoutElementChildren, exitingElementIndex); + found = true; + } + for (int j = 0; j < parentElement->children.length; ++j) { + if (config->exit.siblingOrdering == CLAY_EXIT_TRANSITION_ORDERING_NATURAL_ORDER && j == data->siblingIndex) { + Clay__int32_tArray_Add(&context->layoutElementChildren, exitingElementIndex); + found = true; + } + Clay__int32_tArray_Add(&context->layoutElementChildren, parentElement->children.elements[j]); + } + if (!found) { + Clay__int32_tArray_Add(&context->layoutElementChildren, exitingElementIndex); + } + parentElement->children.length++; + parentElement->children.elements = &context->layoutElementChildren.internalArray[newChildrenStartIndex]; + } + // Parent exited, just delete child without exit transition + } else { + Clay__TransitionDataInternalArray_RemoveSwapback(&context->transitionDatas, i); + i--; + continue; + } + } + // Transition element exited and doesn't have an exit handler defined, delete the transition data + } else if (hashMapItem->generation <= context->generation) { + Clay__TransitionDataInternalArray_RemoveSwapback(&context->transitionDatas, i); + i--; + continue; + } } + if (context->booleanWarnings.maxElementsExceeded) { Clay_String message; - if (!elementsExceededBeforeDebugView) { - message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount after adding the debug-view to the layout."); - } else { - message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount"); - } + message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount"); Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, .renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .textColor = {255, 0, 0, 255}, .fontSize = 16 } }, .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT }); + } else { + if (context->transitionDatas.length > 0) { + Clay__CalculateFinalLayout(deltaTime, false, false); + + for (int i = 0; i < context->transitionDatas.length; ++i) { + Clay__TransitionDataInternal* transitionData = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, i); + Clay_LayoutElement* currentElement = transitionData->elementThisFrame; + Clay_LayoutElementHashMapItem* mapItem = Clay__GetHashMapItem(transitionData->elementId); + Clay_LayoutElementHashMapItem* parentMapItem = Clay__GetHashMapItem(transitionData->parentId); + Clay_TransitionData targetState = transitionData->targetState; + if (transitionData->state != CLAY_TRANSITION_STATE_EXITING) { + targetState = CLAY__INIT(Clay_TransitionData) { + mapItem->boundingBox, + currentElement->config.backgroundColor, + currentElement->config.overlayColor, + currentElement->config.border.color, + currentElement->config.border.width, + }; + } + Clay_TransitionData oldTargetState = transitionData->targetState; + transitionData->targetState = targetState; + if (mapItem->appearedThisFrame) { + if (currentElement->config.transition.enter.setInitialState && !(parentMapItem->appearedThisFrame && currentElement->config.transition.enter.trigger == CLAY_TRANSITION_ENTER_SKIP_ON_FIRST_PARENT_FRAME)) { + transitionData->state = CLAY_TRANSITION_STATE_ENTERING; + transitionData->initialState = currentElement->config.transition.enter.setInitialState(transitionData->targetState, currentElement->config.transition.properties); + transitionData->currentState = transitionData->initialState; + Clay_ApplyTransitionedPropertiesToElement(currentElement, currentElement->config.transition.properties, transitionData->initialState, &mapItem->boundingBox, transitionData->reparented); + } else { + transitionData->initialState = targetState; + transitionData->currentState = targetState; + } + } else { + Clay_Vector2 newRelativePosition = { mapItem->boundingBox.x, mapItem->boundingBox.y }; + newRelativePosition.x -= parentMapItem->boundingBox.x; + newRelativePosition.y -= parentMapItem->boundingBox.y; + Clay_TransitionProperty properties = currentElement->config.transition.properties; + int32_t activeProperties = CLAY_TRANSITION_PROPERTY_NONE; + if (properties & CLAY_TRANSITION_PROPERTY_X) { + if (!Clay__FloatEqual(oldTargetState.boundingBox.x, targetState.boundingBox.x) && !context->rootResizedLastFrame) { + activeProperties |= CLAY_TRANSITION_PROPERTY_X; + } + } + if (properties & CLAY_TRANSITION_PROPERTY_Y) { + if (!Clay__FloatEqual(oldTargetState.boundingBox.y, targetState.boundingBox.y) && !context->rootResizedLastFrame) { + activeProperties |= CLAY_TRANSITION_PROPERTY_Y; + } + } + if (properties & CLAY_TRANSITION_PROPERTY_WIDTH) { + if (!Clay__FloatEqual(oldTargetState.boundingBox.width, targetState.boundingBox.width) && !context->rootResizedLastFrame) { + activeProperties |= CLAY_TRANSITION_PROPERTY_WIDTH; + } + } + if (properties & CLAY_TRANSITION_PROPERTY_HEIGHT) { + if (!Clay__FloatEqual(oldTargetState.boundingBox.height, targetState.boundingBox.height) && !context->rootResizedLastFrame) { + activeProperties |= CLAY_TRANSITION_PROPERTY_HEIGHT; + } + } + if (properties & CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR) { + if (!Clay__MemCmp((char *) &oldTargetState.backgroundColor, (char *)&targetState.backgroundColor, sizeof(Clay_Color))) { + activeProperties |= CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR; + } + } + if (properties & CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR) { + if (!Clay__MemCmp((char *) &oldTargetState.overlayColor, (char *)&targetState.overlayColor, sizeof(Clay_Color))) { + activeProperties |= CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR; + } + } + if (properties & CLAY_TRANSITION_PROPERTY_BORDER_COLOR) { + if (!Clay__MemCmp((char *) &oldTargetState.borderColor, (char *)&targetState.borderColor, sizeof(Clay_Color))) { + activeProperties |= CLAY_TRANSITION_PROPERTY_BORDER_COLOR; + } + } + if (properties & CLAY_TRANSITION_PROPERTY_BORDER_WIDTH) { + if (!Clay__MemCmp((char *) &oldTargetState.borderWidth, (char *)&targetState.borderWidth, sizeof(Clay_BorderWidth))) { + activeProperties |= CLAY_TRANSITION_PROPERTY_BORDER_WIDTH; + } + } + + if (activeProperties != 0 && transitionData->state != CLAY_TRANSITION_STATE_EXITING) { + transitionData->elapsedTime = 0; + transitionData->initialState = transitionData->currentState; + transitionData->state = CLAY_TRANSITION_STATE_TRANSITIONING; + transitionData->activeProperties = (Clay_TransitionProperty)activeProperties; + } + + if (transitionData->state == CLAY_TRANSITION_STATE_IDLE) { + transitionData->initialState = targetState; + transitionData->currentState = targetState; + transitionData->targetState = targetState; + } else { + bool transitionComplete = true; + transitionComplete = currentElement->config.transition.handler(CLAY__INIT(Clay_TransitionCallbackArguments) { + transitionData->state, + transitionData->initialState, + &transitionData->currentState, + targetState, + transitionData->elapsedTime, + currentElement->config.transition.duration, + currentElement->config.transition.properties + }); + + Clay_ApplyTransitionedPropertiesToElement(currentElement, currentElement->config.transition.properties, transitionData->currentState, &mapItem->boundingBox, transitionData->reparented); + transitionData->elapsedTime += deltaTime; + + if (transitionComplete) { + if (transitionData->state == CLAY_TRANSITION_STATE_ENTERING || transitionData->state == CLAY_TRANSITION_STATE_TRANSITIONING) {transitionData->state = CLAY_TRANSITION_STATE_IDLE; + transitionData->elapsedTime = 0; + transitionData->reparented = false; + transitionData->activeProperties = CLAY_TRANSITION_PROPERTY_NONE; + } else if (transitionData->state == CLAY_TRANSITION_STATE_EXITING) { + Clay__TransitionDataInternalArray_RemoveSwapback(&context->transitionDatas, i); + } + } + } + } + } + + if (context->debugModeEnabled) { + context->warningsEnabled = false; + Clay__RenderDebugView(); + context->warningsEnabled = true; + } + + if (context->booleanWarnings.maxElementsExceeded) { + Clay_String message; + message = CLAY_STRING("Clay Error: Debug view caused layout element count to exceed Clay__maxElementCount"); + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { + .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, + .renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .textColor = {255, 0, 0, 255}, .fontSize = 16 } }, + .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT + }); + } else { + Clay__CalculateFinalLayout(deltaTime, true, true); + Clay__CloneElementsWithExitTransition(); + } + } else { + if (context->debugModeEnabled) { + context->warningsEnabled = false; + Clay__RenderDebugView(); + context->warningsEnabled = true; + } + + if (context->booleanWarnings.maxElementsExceeded) { + Clay_String message; + message = CLAY_STRING("Clay Error: Debug view caused layout element count to exceed Clay__maxElementCount"); + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { + .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, + .renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .textColor = {255, 0, 0, 255}, .fontSize = 16 } }, + .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT + }); + } else { + Clay__CalculateFinalLayout(deltaTime, false, true); + } + } } if (context->openLayoutElementStack.length > 1) { context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { @@ -4260,10 +4665,14 @@ Clay_RenderCommandArray Clay_EndLayout(void) { .errorText = CLAY_STRING("There were still open layout elements when EndLayout was called. This results from an unequal number of calls to Clay__OpenElement and Clay__CloseElement."), .userData = context->errorHandler.userData }); } - Clay__CalculateFinalLayout(); return context->renderCommands; } +CLAY_WASM_EXPORT("Clay_GetOpenElementId") +uint32_t Clay_GetOpenElementId(void) { + return Clay__GetOpenLayoutElement()->id; +} + CLAY_WASM_EXPORT("Clay_GetElementId") Clay_ElementId Clay_GetElementId(Clay_String idString) { return Clay__HashString(idString, 0); @@ -4280,10 +4689,6 @@ bool Clay_Hovered(void) { return false; } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); - // If the element has no id attached at this point, we need to generate one - if (openLayoutElement->id == 0) { - Clay__GenerateIdForAnonymousElement(openLayoutElement); - } for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { if (Clay_ElementIdArray_Get(&context->pointerOverIds, i)->id == openLayoutElement->id) { return true; @@ -4298,9 +4703,6 @@ void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_Pointer return; } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); - if (openLayoutElement->id == 0) { - Clay__GenerateIdForAnonymousElement(openLayoutElement); - } Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(openLayoutElement->id); hashMapItem->onHoverFunction = onHoverFunction; hashMapItem->hoverFunctionUserData = userData; @@ -4323,15 +4725,14 @@ Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id) { for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) { Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); if (scrollContainerData->elementId == id.id) { - Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(scrollContainerData->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; - if (!clipElementConfig) { // This can happen on the first frame before a scroll container is declared + if (!scrollContainerData->layoutElement) { // This can happen on the first frame before a scroll container is declared return CLAY__INIT(Clay_ScrollContainerData) CLAY__DEFAULT_STRUCT; } return CLAY__INIT(Clay_ScrollContainerData) { .scrollPosition = &scrollContainerData->scrollPosition, .scrollContainerDimensions = { scrollContainerData->boundingBox.width, scrollContainerData->boundingBox.height }, .contentDimensions = scrollContainerData->contentSize, - .config = *clipElementConfig, + .config = scrollContainerData->layoutElement->config.clip, .found = true }; } @@ -4417,7 +4818,7 @@ void Clay_ResetMeasureTextCache(void) { context->measureTextHashMap.length = 0; context->measuredWords.length = 0; context->measuredWordsFreeList.length = 0; - + for (int32_t i = 0; i < context->measureTextHashMap.capacity; ++i) { context->measureTextHashMap.internalArray[i] = 0; } diff --git a/examples/cairo-pdf-rendering/main.c b/examples/cairo-pdf-rendering/main.c index 3c0946d..9a26a58 100644 --- a/examples/cairo-pdf-rendering/main.c +++ b/examples/cairo-pdf-rendering/main.c @@ -153,7 +153,7 @@ int main(void) { // Moved into a separate function for brevity. Layout(); - Clay_RenderCommandArray commands = Clay_EndLayout(); + Clay_RenderCommandArray commands = Clay_EndLayout(0); // Pass our layout to the cairo backend Clay_Cairo_Render(commands, fonts); diff --git a/examples/clay-official-website/main.c b/examples/clay-official-website/main.c index 4bd3e7a..186ab07 100644 --- a/examples/clay-official-website/main.c +++ b/examples/clay-official-website/main.c @@ -110,7 +110,7 @@ void LandingPageMobile() { void FeatureBlocksDesktop() { CLAY(CLAY_ID("FeatureBlocksOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) } } }) { CLAY(CLAY_ID("FeatureBlocksInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED } }) { - Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED }); + Clay_TextElementConfig textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED }); CLAY(CLAY_ID("HFileBoxOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 50, 32, 32}, .childGap = 8 } }) { CLAY(CLAY_ID("HFileIncludeOuter"), { .layout = { .padding = {8, 4} }, .backgroundColor = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { CLAY_TEXT(CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT })); @@ -129,7 +129,7 @@ void FeatureBlocksDesktop() { void FeatureBlocksMobile() { CLAY(CLAY_ID("FeatureBlocksInner"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) } }, .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED } }) { - Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED }); + Clay_TextElementConfig textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED }); CLAY(CLAY_ID("HFileBoxOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 8 } }) { CLAY(CLAY_ID("HFileIncludeOuter"), { .layout = { .padding = {8, 4} }, .backgroundColor = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { CLAY_TEXT(CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT })); @@ -342,7 +342,7 @@ Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) { Clay_BeginLayout(); CLAY(CLAY_ID("OuterContainer"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) { CLAY(CLAY_ID("Header"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(50) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .childGap = 16, .padding = { 32, 32 } } }) { - CLAY_TEXT(CLAY_STRING("Clay"), &headerTextConfig); + CLAY_TEXT(CLAY_STRING("Clay"), headerTextConfig); CLAY(CLAY_ID("Spacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} if (!mobileScreen) { CLAY(CLAY_ID("LinkExamplesOuter"), { .layout = { .padding = {8, 8} } }) { @@ -426,7 +426,7 @@ Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) { .cornerRadius = CLAY_CORNER_RADIUS(5) }) {} } - return Clay_EndLayout(); + return Clay_EndLayout(0); } bool debugModeEnabled = false; diff --git a/examples/cpp-project-example/main.cpp b/examples/cpp-project-example/main.cpp index 3770d29..eb944a8 100644 --- a/examples/cpp-project-example/main.cpp +++ b/examples/cpp-project-example/main.cpp @@ -16,6 +16,6 @@ int main(void) { CLAY_AUTO_ID({ .layout = layoutElement, .backgroundColor = {255,255,255,0} }) { CLAY_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 })); } - Clay_EndLayout(); + Clay_EndLayout(0); return 0; } diff --git a/examples/raylib-sidebar-scrolling-container/main.c b/examples/raylib-sidebar-scrolling-container/main.c index 3adda43..480511a 100644 --- a/examples/raylib-sidebar-scrolling-container/main.c +++ b/examples/raylib-sidebar-scrolling-container/main.c @@ -38,7 +38,7 @@ Clay_TextElementConfig dropdownTextElementConfig = { .fontSize = 24, .textColor void RenderDropdownTextItem(int index) { CLAY_AUTO_ID({ .layout = dropdownTextItemLayout, .backgroundColor = {180, 180, 180, 255} }) { - CLAY_TEXT(CLAY_STRING("I'm a text field in a scroll container."), &dropdownTextElementConfig); + CLAY_TEXT(CLAY_STRING("I'm a text field in a scroll container."), dropdownTextElementConfig); } } @@ -93,12 +93,12 @@ Clay_RenderCommandArray CreateLayout(void) { CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); CLAY(CLAY_ID("Photos"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, .childGap = 16, .padding = {16, 16, 16, 16} }, .backgroundColor = {180, 180, 220, 255} }) { - CLAY(CLAY_ID("Picture2"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120) }}, .aspectRatio = 1, .image = { .imageData = &profilePicture }}) {} + CLAY(CLAY_ID("Picture2"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120) }}, .aspectRatio = 0.5, .image = { .imageData = &profilePicture }}) {} CLAY(CLAY_ID("Picture1"), { .layout = { .childAlignment = { .x = CLAY_ALIGN_X_CENTER }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {8, 8, 8, 8} }, .backgroundColor = {170, 170, 220, 255} }) { CLAY(CLAY_ID("ProfilePicture2"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture }}) {} CLAY_TEXT(CLAY_STRING("Image caption below"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); } - CLAY(CLAY_ID("Picture3"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120) }}, .aspectRatio = 1, .image = { .imageData = &profilePicture }}) {} + CLAY(CLAY_ID("Picture3"), { .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120) }}, .aspectRatio = 2, .image = { .imageData = &profilePicture }}) {} } CLAY_TEXT(CLAY_STRING("Amet cursus sit amet dictum sit amet justo donec. Et malesuada fames ac turpis egestas maecenas. A lacus vestibulum sed arcu non odio euismod lacinia. Gravida neque convallis a cras. Dui nunc mattis enim ut tellus elementum sagittis vitae et. Orci sagittis eu volutpat odio facilisis mauris. Neque gravida in fermentum et sollicitudin ac orci. Ultrices dui sapien eget mi proin sed libero. Euismod quis viverra nibh cras pulvinar mattis. Diam volutpat commodo sed egestas egestas. In fermentum posuere urna nec tincidunt praesent semper. Integer eget aliquet nibh praesent tristique magna.\nId cursus metus aliquam eleifend mi in. Sed pulvinar proin gravida hendrerit lectus a. Etiam tempor orci eu lobortis elementum nibh tellus. Nullam vehicula ipsum a arcu cursus vitae. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Condimentum lacinia quis vel eros donec ac odio. Mattis pellentesque id nibh tortor id aliquet lectus. Turpis egestas integer eget aliquet nibh praesent tristique. Porttitor massa id neque aliquam vestibulum morbi. Mauris commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Nunc scelerisque viverra mauris in aliquam sem fringilla. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla.\nLacus laoreet non curabitur gravida arcu ac tortor dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Tristique senectus et netus et malesuada fames ac. Nunc aliquet bibendum enim facilisis gravida. Egestas maecenas pharetra convallis posuere morbi leo urna molestie. Sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Ac turpis egestas maecenas pharetra convallis posuere morbi leo urna. Viverra vitae congue eu consequat. Aliquet enim tortor at auctor urna. Ornare massa eget egestas purus viverra accumsan in nisl nisi. Elit pellentesque habitant morbi tristique senectus et netus et malesuada.\nSuspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Lobortis feugiat vivamus at augue eget arcu. Vitae justo eget magna fermentum iaculis eu. Gravida rutrum quisque non tellus orci. Ipsum faucibus vitae aliquet nec. Nullam non nisi est sit amet. Nunc consequat interdum varius sit amet mattis vulputate enim. Sem fringilla ut morbi tincidunt augue interdum. Vitae purus faucibus ornare suspendisse. Massa tincidunt nunc pulvinar sapien et. Fringilla ut morbi tincidunt augue interdum velit euismod in. Donec massa sapien faucibus et. Est placerat in egestas erat imperdiet. Gravida rutrum quisque non tellus. Morbi non arcu risus quis varius quam quisque id diam. Habitant morbi tristique senectus et netus et malesuada fames ac. Eget lorem dolor sed viverra.\nOrnare massa eget egestas purus viverra. Varius vel pharetra vel turpis nunc eget lorem. Consectetur purus ut faucibus pulvinar elementum. Placerat in egestas erat imperdiet sed euismod nisi. Interdum velit euismod in pellentesque massa placerat duis ultricies lacus. Aliquam nulla facilisi cras fermentum odio eu. Est pellentesque elit ullamcorper dignissim cras tincidunt. Nunc sed id semper risus in hendrerit gravida rutrum. A pellentesque sit amet porttitor eget dolor morbi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Sed id semper risus in hendrerit gravida. Tincidunt praesent semper feugiat nibh. Aliquet lectus proin nibh nisl condimentum id venenatis a. Enim sit amet venenatis urna cursus eget. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Lacinia quis vel eros donec ac odio tempor orci. Donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Erat pellentesque adipiscing commodo elit at.\nEgestas sed sed risus pretium quam vulputate. Vitae congue mauris rhoncus aenean vel elit scelerisque mauris pellentesque. Aliquam malesuada bibendum arcu vitae elementum. Congue mauris rhoncus aenean vel elit scelerisque mauris. Pellentesque dignissim enim sit amet venenatis urna cursus. Et malesuada fames ac turpis egestas sed tempus urna. Vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Nibh cras pulvinar mattis nunc sed blandit libero. Fringilla est ullamcorper eget nulla facilisi etiam dignissim. Aenean euismod elementum nisi quis eleifend quam adipiscing vitae proin. Mauris pharetra et ultrices neque ornare aenean euismod elementum. Ornare quam viverra orci sagittis eu. Odio ut sem nulla pharetra diam sit amet nisl suscipit. Ornare lectus sit amet est. Ullamcorper sit amet risus nullam eget. Tincidunt lobortis feugiat vivamus at augue eget arcu dictum.\nUrna nec tincidunt praesent semper feugiat nibh. Ut venenatis tellus in metus vulputate eu scelerisque felis. Cursus risus at ultrices mi tempus. In pellentesque massa placerat duis ultricies lacus sed turpis. Platea dictumst quisque sagittis purus. Cras adipiscing enim eu turpis egestas. Egestas sed tempus urna et pharetra pharetra. Netus et malesuada fames ac turpis egestas integer eget aliquet. Ac turpis egestas sed tempus. Sed lectus vestibulum mattis ullamcorper velit sed. Ante metus dictum at tempor commodo ullamcorper a. Augue neque gravida in fermentum et sollicitudin ac. Praesent semper feugiat nibh sed pulvinar proin gravida. Metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices. Neque gravida in fermentum et sollicitudin ac orci phasellus egestas.\nRidiculus mus mauris vitae ultricies. Morbi quis commodo odio aenean. Duis ultricies lacus sed turpis. Non pulvinar neque laoreet suspendisse interdum consectetur. Scelerisque eleifend donec pretium vulputate sapien nec sagittis aliquam. Volutpat est velit egestas dui id ornare arcu odio ut. Viverra tellus in hac habitasse platea dictumst vestibulum rhoncus est. Vestibulum lectus mauris ultrices eros. Sed blandit libero volutpat sed cras ornare. Id leo in vitae turpis massa sed elementum tempus. Gravida dictum fusce ut placerat orci nulla pellentesque. Pretium quam vulputate dignissim suspendisse in. Nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Risus viverra adipiscing at in tellus. Turpis nunc eget lorem dolor sed viverra ipsum. Senectus et netus et malesuada fames ac. Habitasse platea dictumst vestibulum rhoncus est. Nunc sed id semper risus in hendrerit gravida. Felis eget velit aliquet sagittis id. Eget felis eget nunc lobortis.\nMaecenas pharetra convallis posuere morbi leo. Maecenas volutpat blandit aliquam etiam. A condimentum vitae sapien pellentesque habitant morbi tristique senectus et. Pulvinar mattis nunc sed blandit libero volutpat sed. Feugiat in ante metus dictum at tempor commodo ullamcorper. Vel pharetra vel turpis nunc eget lorem dolor. Est placerat in egestas erat imperdiet sed euismod. Quisque non tellus orci ac auctor augue mauris augue. Placerat vestibulum lectus mauris ultrices eros in cursus turpis. Enim nunc faucibus a pellentesque sit. Adipiscing vitae proin sagittis nisl. Iaculis at erat pellentesque adipiscing commodo elit at imperdiet. Aliquam sem fringilla ut morbi.\nArcu odio ut sem nulla pharetra diam sit amet nisl. Non diam phasellus vestibulum lorem sed. At erat pellentesque adipiscing commodo elit at. Lacus luctus accumsan tortor posuere ac ut consequat. Et malesuada fames ac turpis egestas integer. Tristique magna sit amet purus. A condimentum vitae sapien pellentesque habitant. Quis varius quam quisque id diam vel quam. Est ullamcorper eget nulla facilisi etiam dignissim diam quis. Augue interdum velit euismod in pellentesque massa. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant. Vulputate eu scelerisque felis imperdiet. Nibh tellus molestie nunc non blandit massa. Velit euismod in pellentesque massa placerat. Sed cras ornare arcu dui. Ut sem viverra aliquet eget sit. Eu lobortis elementum nibh tellus molestie nunc non. Blandit libero volutpat sed cras ornare arcu dui vivamus.\nSit amet aliquam id diam maecenas. Amet risus nullam eget felis eget nunc lobortis mattis aliquam. Magna sit amet purus gravida. Egestas purus viverra accumsan in nisl nisi. Leo duis ut diam quam. Ante metus dictum at tempor commodo ullamcorper. Ac turpis egestas integer eget. Fames ac turpis egestas integer eget aliquet nibh. Sem integer vitae justo eget magna fermentum. Semper auctor neque vitae tempus quam pellentesque nec nam aliquam. Vestibulum mattis ullamcorper velit sed. Consectetur adipiscing elit duis tristique sollicitudin nibh. Massa id neque aliquam vestibulum morbi blandit cursus risus.\nCursus sit amet dictum sit amet justo donec enim diam. Egestas erat imperdiet sed euismod. Nullam vehicula ipsum a arcu cursus vitae congue mauris. Habitasse platea dictumst vestibulum rhoncus est pellentesque elit. Duis ultricies lacus sed turpis tincidunt id aliquet risus feugiat. Faucibus ornare suspendisse sed nisi lacus sed viverra. Pretium fusce id velit ut tortor pretium viverra. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel. Senectus et netus et malesuada. Tellus pellentesque eu tincidunt tortor aliquam. Aenean sed adipiscing diam donec adipiscing tristique risus nec feugiat. Quis vel eros donec ac odio. Id interdum velit laoreet id donec ultrices tincidunt.\nMassa id neque aliquam vestibulum morbi blandit cursus risus at. Enim tortor at auctor urna nunc id cursus metus. Lorem ipsum dolor sit amet consectetur. At quis risus sed vulputate odio. Facilisis mauris sit amet massa vitae tortor condimentum lacinia quis. Et malesuada fames ac turpis egestas maecenas. Bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim. Viverra orci sagittis eu volutpat odio facilisis mauris. Adipiscing bibendum est ultricies integer quis auctor elit sed. Neque viverra justo nec ultrices dui sapien. Elementum nibh tellus molestie nunc non blandit massa enim. Euismod elementum nisi quis eleifend quam adipiscing vitae proin sagittis. Faucibus ornare suspendisse sed nisi. Quis viverra nibh cras pulvinar mattis nunc sed blandit. Tristique senectus et netus et. Magnis dis parturient montes nascetur ridiculus mus.\nDolor magna eget est lorem ipsum dolor. Nibh sit amet commodo nulla. Donec pretium vulputate sapien nec sagittis aliquam malesuada. Cras adipiscing enim eu turpis egestas pretium. Cras ornare arcu dui vivamus arcu felis bibendum ut tristique. Mus mauris vitae ultricies leo integer. In nulla posuere sollicitudin aliquam ultrices sagittis orci. Quis hendrerit dolor magna eget. Nisl tincidunt eget nullam non. Vitae congue eu consequat ac felis donec et odio. Vivamus at augue eget arcu dictum varius duis at. Ornare quam viverra orci sagittis.\nErat nam at lectus urna duis convallis. Massa placerat duis ultricies lacus sed turpis tincidunt id aliquet. Est ullamcorper eget nulla facilisi etiam dignissim diam. Arcu vitae elementum curabitur vitae nunc sed velit dignissim sodales. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus. Neque viverra justo nec ultrices dui sapien eget mi proin. Viverra accumsan in nisl nisi scelerisque eu ultrices. Consequat interdum varius sit amet mattis. In aliquam sem fringilla ut morbi. Eget arcu dictum varius duis at. Nulla aliquet porttitor lacus luctus accumsan tortor posuere. Arcu bibendum at varius vel pharetra vel turpis. Hac habitasse platea dictumst quisque sagittis purus sit amet. Sapien eget mi proin sed libero enim sed. Quam elementum pulvinar etiam non quam lacus suspendisse faucibus interdum. Semper viverra nam libero justo. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Et malesuada fames ac turpis egestas maecenas pharetra convallis posuere.\nTurpis egestas sed tempus urna et pharetra pharetra massa. Gravida in fermentum et sollicitudin ac orci phasellus. Ornare suspendisse sed nisi lacus sed viverra tellus in. Fames ac turpis egestas maecenas pharetra convallis posuere. Mi proin sed libero enim sed faucibus turpis. Sit amet mauris commodo quis imperdiet massa tincidunt nunc. Ut etiam sit amet nisl purus in mollis nunc. Habitasse platea dictumst quisque sagittis purus sit amet volutpat consequat. Eget aliquet nibh praesent tristique magna. Sit amet est placerat in egestas erat. Commodo sed egestas egestas fringilla. Enim nulla aliquet porttitor lacus luctus accumsan tortor posuere ac. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Dignissim convallis aenean et tortor at risus viverra. Morbi blandit cursus risus at ultrices mi. Ac turpis egestas integer eget aliquet nibh praesent tristique magna.\nVolutpat sed cras ornare arcu dui. Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam. Viverra justo nec ultrices dui sapien. Amet risus nullam eget felis eget nunc lobortis. Metus aliquam eleifend mi in. Ut eu sem integer vitae. Auctor elit sed vulputate mi sit amet. Nisl nisi scelerisque eu ultrices. Dictum fusce ut placerat orci nulla. Pellentesque habitant morbi tristique senectus et. Auctor elit sed vulputate mi sit. Tincidunt arcu non sodales neque. Mi in nulla posuere sollicitudin aliquam. Morbi non arcu risus quis varius quam quisque id diam. Cras adipiscing enim eu turpis egestas pretium aenean pharetra magna. At auctor urna nunc id cursus metus aliquam. Mauris a diam maecenas sed enim ut sem viverra. Nunc scelerisque viverra mauris in. In iaculis nunc sed augue lacus viverra vitae congue eu. Volutpat blandit aliquam etiam erat velit scelerisque in dictum non."), @@ -139,7 +139,7 @@ Clay_RenderCommandArray CreateLayout(void) { } } } - return Clay_EndLayout(); + return Clay_EndLayout(GetFrameTime()); } typedef struct diff --git a/examples/raylib-transitions/CMakeLists.txt b/examples/raylib-transitions/CMakeLists.txt new file mode 100644 index 0000000..3546908 --- /dev/null +++ b/examples/raylib-transitions/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.27) +project(clay_examples_raylib_transitions C) +set(CMAKE_C_STANDARD 99) + +# Adding Raylib +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) +set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples +set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games + +FetchContent_Declare( + raylib + GIT_REPOSITORY "https://github.com/raysan5/raylib.git" + GIT_TAG "5.5" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE +) + +FetchContent_MakeAvailable(raylib) + +add_executable(clay_examples_raylib_transitions main.c) + +target_compile_options(clay_examples_raylib_transitions PUBLIC) +target_include_directories(clay_examples_raylib_transitions PUBLIC .) + +target_link_libraries(clay_examples_raylib_transitions PUBLIC raylib) +if(MSVC) + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") +else() + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") +endif() + +add_custom_command( + TARGET clay_examples_raylib_transitions POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/resources + ${CMAKE_CURRENT_BINARY_DIR}/resources) diff --git a/examples/raylib-transitions/main.c b/examples/raylib-transitions/main.c new file mode 100644 index 0000000..66f84b6 --- /dev/null +++ b/examples/raylib-transitions/main.c @@ -0,0 +1,422 @@ +#define CLAY_IMPLEMENTATION +#include "../../clay.h" +#include "../../renderers/raylib/clay_renderer_raylib.c" + +const uint32_t FONT_ID_BODY_24 = 0; +const uint32_t FONT_ID_BODY_16 = 1; +#define COLOR_ORANGE (Clay_Color) {225, 138, 50, 255} +#define COLOR_BLUE (Clay_Color) {111, 173, 162, 255} + +Texture2D profilePicture; +#define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = vector.x, .y = vector.y } + +Clay_String profileText = CLAY_STRING_CONST("Profile Page one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen"); +Clay_TextElementConfig headerTextConfig = { .fontId = 1, .letterSpacing = 5, .fontSize = 16, .textColor = {0,0,0,255} }; + +void HandleHeaderButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData) { + if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + // Do some click handling + } +} + +Clay_ElementDeclaration HeaderButtonStyle(bool hovered) { + return (Clay_ElementDeclaration) { + .layout = {.padding = {16, 16, 8, 8}}, + .backgroundColor = hovered ? COLOR_ORANGE : COLOR_BLUE, + }; +} + +// Examples of re-usable "Components" +void RenderHeaderButton(Clay_String text) { + CLAY_AUTO_ID(HeaderButtonStyle(Clay_Hovered())) { + CLAY_TEXT(text, CLAY_TEXT_CONFIG(headerTextConfig)); + } +} + +Clay_LayoutConfig dropdownTextItemLayout = { .padding = {8, 8, 4, 4} }; +Clay_TextElementConfig dropdownTextElementConfig = { .fontSize = 24, .textColor = {255,255,255,255} }; + +void RenderDropdownTextItem(int index) { + CLAY_AUTO_ID({ .layout = dropdownTextItemLayout, .backgroundColor = {180, 180, 180, 255} }) { + CLAY_TEXT(CLAY_STRING("I'm a text field in a scroll container."), dropdownTextElementConfig); + } +} + +typedef struct { + int id; + Clay_Color color; + const char* stringId; +} SortableBox; + +int maxCount = 30; +int cellCount = 30; +char* charData; +SortableBox colors[100] = {}; + +bool blueColor = 0; + +#define GG { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() } + +#define cWHITE { 255, 255, 255, 255 } + +typedef struct { + int value; +} Test; + +typedef struct { + void* memory; + uintptr_t offset; +} Arena; + +Arena frameArena = {}; + +bool Clay_EaseOut(Clay_TransitionCallbackArguments arguments) { + float ratio = 1; + if (arguments.duration > 0) { + ratio = arguments.elapsedTime / arguments.duration; + } + float lerpAmount = (1 - powf(1 - CLAY__MIN(ratio, 1.f), 3.0f)); + if (arguments.properties & CLAY_TRANSITION_PROPERTY_X) { + arguments.current->boundingBox.x = Lerp(arguments.initial.boundingBox.x, arguments.target.boundingBox.x, lerpAmount); + } + if (arguments.properties & CLAY_TRANSITION_PROPERTY_Y) { + arguments.current->boundingBox.y = Lerp(arguments.initial.boundingBox.y, arguments.target.boundingBox.y, lerpAmount); + } + if (arguments.properties & CLAY_TRANSITION_PROPERTY_WIDTH) { + arguments.current->boundingBox.width = Lerp(arguments.initial.boundingBox.width, arguments.target.boundingBox.width, lerpAmount); + } + if (arguments.properties & CLAY_TRANSITION_PROPERTY_HEIGHT) { + arguments.current->boundingBox.height = Lerp(arguments.initial.boundingBox.height, arguments.target.boundingBox.height, lerpAmount); + } + if (arguments.properties & CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR) { + arguments.current->backgroundColor = CLAY__INIT(Clay_Color) { + .r = Lerp(arguments.initial.backgroundColor.r, arguments.target.backgroundColor.r, lerpAmount), + .g = Lerp(arguments.initial.backgroundColor.g, arguments.target.backgroundColor.g, lerpAmount), + .b = Lerp(arguments.initial.backgroundColor.b, arguments.target.backgroundColor.b, lerpAmount), + .a = Lerp(arguments.initial.backgroundColor.a, arguments.target.backgroundColor.a, lerpAmount), + }; + } + if (arguments.properties & CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR) { + arguments.current->overlayColor = CLAY__INIT(Clay_Color) { + .r = Lerp(arguments.initial.overlayColor.r, arguments.target.overlayColor.r, lerpAmount), + .g = Lerp(arguments.initial.overlayColor.g, arguments.target.overlayColor.g, lerpAmount), + .b = Lerp(arguments.initial.overlayColor.b, arguments.target.overlayColor.b, lerpAmount), + .a = Lerp(arguments.initial.overlayColor.a, arguments.target.overlayColor.a, lerpAmount), + }; + } + if (arguments.properties & CLAY_TRANSITION_PROPERTY_BORDER_COLOR) { + arguments.current->borderColor = CLAY__INIT(Clay_Color) { + .r = Lerp(arguments.initial.borderColor.r, arguments.target.borderColor.r, lerpAmount), + .g = Lerp(arguments.initial.borderColor.g, arguments.target.borderColor.g, lerpAmount), + .b = Lerp(arguments.initial.borderColor.b, arguments.target.borderColor.b, lerpAmount), + .a = Lerp(arguments.initial.borderColor.a, arguments.target.borderColor.a, lerpAmount), + }; + } + if (arguments.properties & CLAY_TRANSITION_PROPERTY_BORDER_WIDTH) { + arguments.current->borderWidth = CLAY__INIT(Clay_BorderWidth) { + .left = Lerp(arguments.initial.borderWidth.left, arguments.target.borderWidth.left, lerpAmount), + .right = Lerp(arguments.initial.borderWidth.right, arguments.target.borderWidth.right, lerpAmount), + .top = Lerp(arguments.initial.borderWidth.top, arguments.target.borderWidth.top, lerpAmount), + .bottom = Lerp(arguments.initial.borderWidth.bottom, arguments.target.borderWidth.bottom, lerpAmount), + .betweenChildren = Lerp(arguments.initial.borderWidth.betweenChildren, arguments.target.borderWidth.betweenChildren, lerpAmount), + }; + } + return ratio >= 1; +} + +Clay_TransitionData EnterExitSlideUp(Clay_TransitionData initialState, Clay_TransitionProperty properties) { + Clay_TransitionData targetState = initialState; + if (properties & CLAY_TRANSITION_PROPERTY_Y) { + targetState.boundingBox.y += 20; + } + if (properties & CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR) { + targetState.overlayColor = (Clay_Color) { 255, 255, 255, 255 }; + } + return targetState; +} +// Swaps two elements in an array +void swap(SortableBox *a, SortableBox *b) { + SortableBox temp = *a; + *a = *b; + *b = temp; +} + +void shuffle(SortableBox *array, size_t n) { + if (n <= 1) return; + + for (size_t i = n - 1; i > 0; i--) { + size_t j = rand() % (i + 1); + swap(&array[i], &array[j]); + } +} + +void add(SortableBox *array, int32_t length, int32_t index, SortableBox toAdd) { + for (int i = length; i > index; --i) { + array[i] = array[i - 1]; + } + array[index] = toAdd; +} + +void HandleRandomiseButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData) { + if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + shuffle(colors, cellCount); + } +} + +void HandlePinkButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData) { + if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + for (int i = 0; i < cellCount; i++) { + int index = colors[i].id; + colors[i] = (SortableBox) { + .id = index, + .color = { 255 - index, 255 - index * 4, 255 - index * 2, 255 }, + .stringId = colors[i].stringId + }; + } + } +} + +void HandleNewButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData) { + if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + int32_t randomIndex = rand() % (cellCount + 1); + int32_t newId = maxCount; + snprintf(&charData[newId * 3], 3, "%02d", newId); + + add(colors, maxCount, randomIndex, (SortableBox) { + .id = newId, + .color = { 255 - newId, 255 - newId * 4, 255 - newId * 2, 255 }, + .stringId = &charData[newId * 3] + }); + + cellCount++; + maxCount++; + } +} + +void HandleBlueButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData) { + if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + for (int i = 0; i < cellCount; i++) { + int index = colors[i].id; + colors[i] = (SortableBox) { + .id = index, + .color = { 255 - index * 4, 255 - index * 2, 255 - index, 255 }, + .stringId = colors[i].stringId + }; + } + } +} + +void HandleCellButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData) { + if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + for (int i = (uintptr_t)userData; i < cellCount; i++) { + colors[i] = colors[i + 1]; + } + cellCount = CLAY__MAX(cellCount - 1, 0); + } +} + +Clay_RenderCommandArray CreateLayout(void) { + frameArena.offset = 0; + Clay_BeginLayout(); + CLAY(CLAY_ID("OuterContainer"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, .padding = { 16, 16, 16, 16 }, .childGap = 12 }, .backgroundColor = cWHITE }) { + CLAY_AUTO_ID({ .layout.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(60) }, .layout.padding.left = 16, .layout.childGap = 16, .layout.childAlignment.y = CLAY_ALIGN_Y_CENTER, .cornerRadius = { 12, 12, 12, 12 }, .backgroundColor = {174, 143, 204, 255 } }) { + CLAY(CLAY_ID("ShuffleButton"), { + .backgroundColor = Clay_Hovered() ? (Clay_Color){ 154, 123, 184, 255 } : (Clay_Color){ }, + .layout.padding = { 16, 16, 8, 8 }, + .cornerRadius = CLAY_CORNER_RADIUS(6), + .border = { .color = cWHITE, .width = CLAY_BORDER_OUTSIDE(2) }, + }) { + Clay_OnHover(HandleRandomiseButtonInteraction, 0); + CLAY_TEXT(CLAY_STRING("Randomise"), CLAY_TEXT_CONFIG({ .fontSize = 20, .textColor = cWHITE })); + } + CLAY(CLAY_ID("bluebutton"), { + .backgroundColor = Clay_Hovered() ? (Clay_Color){ 154, 123, 184, 255 } : (Clay_Color){ }, + .layout.padding = { 16, 16, 8, 8 }, + .cornerRadius = CLAY_CORNER_RADIUS(6), + .border = { .color = cWHITE, .width = CLAY_BORDER_OUTSIDE(2) }, + }) { + Clay_OnHover(HandleBlueButtonInteraction, 0); + CLAY_TEXT(CLAY_STRING("Blue"), CLAY_TEXT_CONFIG({ .fontSize = 20, .textColor = cWHITE })); + } + CLAY(CLAY_ID("PinkButton"), { + .backgroundColor = Clay_Hovered() ? (Clay_Color){ 154, 123, 184, 255 } : (Clay_Color){ }, + .layout.padding = { 16, 16, 8, 8 }, + .cornerRadius = CLAY_CORNER_RADIUS(6), + .border = { .color = cWHITE, .width = CLAY_BORDER_OUTSIDE(2) }, + }) { + Clay_OnHover(HandlePinkButtonInteraction, 0); + CLAY_TEXT(CLAY_STRING("Pink"), CLAY_TEXT_CONFIG({ .fontSize = 20, .textColor = cWHITE })); + } + CLAY(CLAY_ID("AddButton"), { + .backgroundColor = Clay_Hovered() ? (Clay_Color){ 154, 123, 184, 255 } : (Clay_Color){ }, + .layout.padding = { 16, 16, 8, 8 }, + .cornerRadius = CLAY_CORNER_RADIUS(6), + .border = { .color = cWHITE, .width = CLAY_BORDER_OUTSIDE(2) }, + }) { + Clay_OnHover(HandleNewButtonInteraction, 0); + CLAY_TEXT(CLAY_STRING("Add Box"), CLAY_TEXT_CONFIG({ .fontSize = 20, .textColor = cWHITE })); + } + } + for (int i = 0; i < 5; i++) { + Clay_ElementId rowId = CLAY_IDI("row", i); + Clay_ElementData rowData = Clay_GetElementData(rowId); + CLAY(rowId, { .layout.childGap = 12, .layout.sizing = GG }) { + for (int j = 0; j < 6; j++) { + int index = i * 6 + j; + if (index >= cellCount) { + break; + } + Clay_Color boxColor = colors[index].color; + Clay_Color darker = { boxColor.r * 0.9, boxColor.g * 0.9, boxColor.b * 0.9, 255 }; + CLAY(CLAY_IDI("box", colors[index].id), { + .layout.sizing = {CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, + .layout.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }, + .backgroundColor = boxColor, + .overlayColor = Clay_Hovered() ? (Clay_Color) { 140, 140, 140, 80 } : (Clay_Color) { 255, 255, 255, 0 }, + .cornerRadius = {12, 12, 12, 12}, + .border = { darker, CLAY_BORDER_OUTSIDE(3) }, + .transition = { + .handler = Clay_EaseOut, + .duration = Clay_Hovered() && Clay_GetPointerState().state != CLAY_POINTER_DATA_PRESSED_THIS_FRAME ? 0.f : 0.5f, + .properties = CLAY_TRANSITION_PROPERTY_WIDTH | CLAY_TRANSITION_PROPERTY_POSITION | CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR | CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR, + .enter = { .setInitialState = EnterExitSlideUp }, + .exit = { .setFinalState = EnterExitSlideUp }, + } + }) { + Clay_OnHover(HandleCellButtonInteraction, (void*)(uint64_t)index); + CLAY_TEXT(((Clay_String) { .length = 2, .chars = colors[index].stringId, .isStaticallyAllocated = true }), CLAY_TEXT_CONFIG({ + .fontSize = 32, + .textColor = colors[index].id > 29 ? (Clay_Color) {255, 255, 255, 255} : (Clay_Color) {154, 123, 184, 255 } + })); + } + } + } + } + } + return Clay_EndLayout(GetFrameTime()); +} + +typedef struct +{ + Clay_Vector2 clickOrigin; + Clay_Vector2 positionOrigin; + bool mouseDown; +} ScrollbarData; + +ScrollbarData scrollbarData = {0}; + +bool debugEnabled = false; + +void UpdateDrawFrame(Font* fonts) +{ + Vector2 mouseWheelDelta = GetMouseWheelMoveV(); + float mouseWheelX = mouseWheelDelta.x; + float mouseWheelY = mouseWheelDelta.y; + + if (IsKeyPressed(KEY_D)) { + debugEnabled = !debugEnabled; + Clay_SetDebugModeEnabled(debugEnabled); + } + //---------------------------------------------------------------------------------- + // Handle scroll containers + Clay_Vector2 mousePosition = RAYLIB_VECTOR2_TO_CLAY_VECTOR2(GetMousePosition()); + Clay_SetPointerState(mousePosition, IsMouseButtonDown(0) && !scrollbarData.mouseDown); + Clay_SetLayoutDimensions((Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }); + if (!IsMouseButtonDown(0)) { + scrollbarData.mouseDown = false; + } + + if (IsMouseButtonDown(0) && !scrollbarData.mouseDown && Clay_PointerOver(Clay__HashString(CLAY_STRING("ScrollBar"), 0))) { + Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay__HashString(CLAY_STRING("MainContent"), 0)); + scrollbarData.clickOrigin = mousePosition; + scrollbarData.positionOrigin = *scrollContainerData.scrollPosition; + scrollbarData.mouseDown = true; + } else if (scrollbarData.mouseDown) { + Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay__HashString(CLAY_STRING("MainContent"), 0)); + if (scrollContainerData.contentDimensions.height > 0) { + Clay_Vector2 ratio = (Clay_Vector2) { + scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width, + scrollContainerData.contentDimensions.height / scrollContainerData.scrollContainerDimensions.height, + }; + if (scrollContainerData.config.vertical) { + scrollContainerData.scrollPosition->y = scrollbarData.positionOrigin.y + (scrollbarData.clickOrigin.y - mousePosition.y) * ratio.y; + } + if (scrollContainerData.config.horizontal) { + scrollContainerData.scrollPosition->x = scrollbarData.positionOrigin.x + (scrollbarData.clickOrigin.x - mousePosition.x) * ratio.x; + } + } + } + + Clay_UpdateScrollContainers(true, (Clay_Vector2) {mouseWheelX, mouseWheelY}, GetFrameTime()); + // Generate the auto layout for rendering + double currentTime = GetTime(); + Clay_RenderCommandArray renderCommands = CreateLayout(); + printf("layout time: %f microseconds\n", (GetTime() - currentTime) * 1000 * 1000); + // RENDERING --------------------------------- +// currentTime = GetTime(); + BeginDrawing(); + ClearBackground(BLACK); + Clay_Raylib_Render(renderCommands, fonts); + EndDrawing(); +// printf("render time: %f ms\n", (GetTime() - currentTime) * 1000); + + //---------------------------------------------------------------------------------- +} + +bool reinitializeClay = false; + +void HandleClayErrors(Clay_ErrorData errorData) { + printf("%s\n", errorData.errorText.chars); + if (errorData.errorType == CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED) { + reinitializeClay = true; + Clay_SetMaxElementCount(Clay_GetMaxElementCount() * 2); + } else if (errorData.errorType == CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED) { + reinitializeClay = true; + Clay_SetMaxMeasureTextCacheWordCount(Clay_GetMaxMeasureTextCacheWordCount() * 2); + } +} + +int main(void) { + uint64_t totalMemorySize = Clay_MinMemorySize(); + Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); + Clay_Context *context = Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }, (Clay_ErrorHandler) { HandleClayErrors, 0 }); + Clay_Raylib_Initialize(1024, 768, "Clay - Raylib Renderer Example", FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_MSAA_4X_HINT); + profilePicture = LoadTexture("resources/profile-picture.png"); + + Font fonts[2]; + fonts[FONT_ID_BODY_24] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400); + SetTextureFilter(fonts[FONT_ID_BODY_24].texture, TEXTURE_FILTER_BILINEAR); + fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 32, 0, 400); + SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR); + Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts); + + frameArena = (Arena) {.memory = malloc(1024) }; + + charData = (char*)malloc(100 * 3); + + for (int i = 0; i < cellCount; i++) { + snprintf(&charData[i * 3], 3, "%02d", i); + colors[i] = (SortableBox) { + .id = i, + .color = { 255 - i, 255 - i * 4, 255 - i * 2, 255 }, + .stringId = &charData[i * 3], + }; + } + + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + if (reinitializeClay) { + Clay_SetMaxElementCount(8192); + totalMemorySize = Clay_MinMemorySize(); + clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); + Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }, (Clay_ErrorHandler) { HandleClayErrors, 0 }); + reinitializeClay = false; + } + UpdateDrawFrame(fonts); + } + Clay_Raylib_Close(); + return 0; +} diff --git a/examples/raylib-transitions/resources/Roboto-Regular.ttf b/examples/raylib-transitions/resources/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ddf4bfacb396e97546364ccfeeb9c31dfaea4c25 GIT binary patch literal 168260 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.y += data->yOffset; } diff --git a/examples/sokol-corner-radius/main.c b/examples/sokol-corner-radius/main.c index 6ae11a5..046cd93 100644 --- a/examples/sokol-corner-radius/main.c +++ b/examples/sokol-corner-radius/main.c @@ -65,7 +65,7 @@ Clay_RenderCommandArray CornerRadiusTest(){ } } } - return Clay_EndLayout(); + return Clay_EndLayout(0); } static void frame() { diff --git a/renderers/raylib/clay_renderer_raylib.c b/renderers/raylib/clay_renderer_raylib.c index 6e56103..7fbc2c7 100644 --- a/renderers/raylib/clay_renderer_raylib.c +++ b/renderers/raylib/clay_renderer_raylib.c @@ -31,6 +31,54 @@ typedef struct } customData; } CustomLayoutElement; +const char* overlayShaderCode = "#version 330\n" + "\n" + "in vec2 fragTexCoord;\n" + "in vec4 fragColor;\n" + "\n" + "uniform sampler2D texture0;\n" + "uniform vec4 overlayColor;\n" + "\n" + "out vec4 finalColor;\n" + "\n" + "void main()\n" + "{\n" + " vec4 texelColor = texture(texture0, fragTexCoord) * fragColor;\n" + "\n" + " vec3 blendedRGB = mix(texelColor.rgb, overlayColor.rgb, overlayColor.a);\n" + "\n" + " finalColor = vec4(blendedRGB, texelColor.a);\n" + "}"; + +Shader overlayShader; +int colorLoc; +bool overlayEnabled = false; + +void InitOverlay() { + overlayShader = LoadShaderFromMemory(0, overlayShaderCode); + colorLoc = GetShaderLocation(overlayShader, "overlayColor"); +} + +void SetColorOverlay(Color color) { + overlayEnabled = true; + float colorFloat[4] = { + (float)color.r/255.0f, + (float)color.g/255.0f, + (float)color.b/255.0f, + (float)color.a/255.0f, + }; + + SetShaderValue(overlayShader, colorLoc, colorFloat, SHADER_UNIFORM_VEC4); + BeginShaderMode(overlayShader); +} + +void DisableColorOverlay() { + if (overlayEnabled) { + EndShaderMode(); + overlayEnabled = false; + } +} + // 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) { @@ -127,6 +175,7 @@ static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_Tex void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned int flags) { SetConfigFlags(flags); InitWindow(width, height, title); + InitOverlay(); // EnableEventWaiting(); } @@ -150,7 +199,7 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts) for (int j = 0; j < renderCommands.length; j++) { Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); - Clay_BoundingBox boundingBox = {roundf(renderCommand->boundingBox.x), roundf(renderCommand->boundingBox.y), roundf(renderCommand->boundingBox.width), roundf(renderCommand->boundingBox.height)}; + Clay_BoundingBox boundingBox = {renderCommand->boundingBox.x, renderCommand->boundingBox.y, renderCommand->boundingBox.width, renderCommand->boundingBox.height}; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_TEXT: { @@ -196,6 +245,13 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts) EndScissorMode(); break; } + case CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START: { + SetColorOverlay(CLAY_COLOR_TO_RAYLIB_COLOR(renderCommand->renderData.overlayColor.color)); + break; + } + case CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_END: { + DisableColorOverlay(); + } case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; if (config->cornerRadius.topLeft > 0) { @@ -210,19 +266,19 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts) Clay_BorderRenderData *config = &renderCommand->renderData.border; // Left border if (config->width.left > 0) { - DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y + config->cornerRadius.topLeft), (int)config->width.left, (int)roundf(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft), CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); + DrawRectangleV((Vector2) { boundingBox.x, boundingBox.y + config->cornerRadius.topLeft }, (Vector2) { config->width.left, boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft }, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); } // Right border if (config->width.right > 0) { - DrawRectangle((int)roundf(boundingBox.x + boundingBox.width - config->width.right), (int)roundf(boundingBox.y + config->cornerRadius.topRight), (int)config->width.right, (int)roundf(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight), CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); + DrawRectangleV((Vector2) { boundingBox.x + boundingBox.width - config->width.right, boundingBox.y + config->cornerRadius.topRight }, (Vector2) { config->width.right, boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight }, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); } // Top border if (config->width.top > 0) { - DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.topLeft), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight), (int)config->width.top, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); + DrawRectangleV((Vector2) { boundingBox.x + config->cornerRadius.topLeft, boundingBox.y }, (Vector2) { boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight, (int)config->width.top }, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); } // Bottom border if (config->width.bottom > 0) { - DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.bottomLeft), (int)roundf(boundingBox.y + boundingBox.height - config->width.bottom), (int)roundf(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight), (int)config->width.bottom, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); + DrawRectangleV((Vector2) { boundingBox.x + config->cornerRadius.bottomLeft, boundingBox.y + boundingBox.height - config->width.bottom }, (Vector2) { boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight, (int)config->width.bottom }, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); } if (config->cornerRadius.topLeft > 0) { DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.topLeft), roundf(boundingBox.y + config->cornerRadius.topLeft) }, roundf(config->cornerRadius.topLeft - config->width.top), config->cornerRadius.topLeft, 180, 270, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));