clay.h
1 // VERSION: 0.14 2 3 /* 4 NOTE: In order to use this library you must define 5 the following macro in exactly one file, _before_ including clay.h: 6 7 #define CLAY_IMPLEMENTATION 8 #include "clay.h" 9 10 See the examples folder for details. 11 */ 12 13 #include <stdint.h> 14 #include <stdbool.h> 15 #include <stddef.h> 16 17 // SIMD includes on supported platforms 18 #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) 19 #include <emmintrin.h> 20 #elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) 21 #include <arm_neon.h> 22 #endif 23 #if __CLION_IDE__ 24 #define CLAY_IMPLEMENTATION 25 #endif 26 27 // ----------------------------------------- 28 // HEADER DECLARATIONS --------------------- 29 // ----------------------------------------- 30 31 #ifndef CLAY_HEADER 32 #define CLAY_HEADER 33 34 #if !( \ 35 (defined(__cplusplus) && __cplusplus >= 202002L) || \ 36 (defined(__STDC__) && __STDC__ == 1 && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ 37 defined(_MSC_VER) || \ 38 defined(__OBJC__) \ 39 ) 40 #error "Clay requires C99, C++20, or MSVC" 41 #endif 42 43 #ifdef CLAY_WASM 44 #define CLAY_WASM_EXPORT(name) __attribute__((export_name(name))) 45 #else 46 #define CLAY_WASM_EXPORT(null) 47 #endif 48 49 #ifdef CLAY_DLL 50 #define CLAY_DLL_EXPORT __declspec(dllexport) __stdcall 51 #else 52 #define CLAY_DLL_EXPORT 53 #endif 54 55 // Public Macro API ------------------------ 56 57 #define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y)) 58 #define CLAY__MIN(x, y) (((x) < (y)) ? (x) : (y)) 59 60 #define CLAY_TEXT_CONFIG(...) __VA_ARGS__ 61 62 #define CLAY_BORDER_OUTSIDE(widthValue) {widthValue, widthValue, widthValue, widthValue, 0} 63 64 #define CLAY_BORDER_ALL(widthValue) {widthValue, widthValue, widthValue, widthValue, widthValue} 65 66 #define CLAY_CORNER_RADIUS(radius) (CLAY__INIT(Clay_CornerRadius) { radius, radius, radius, radius }) 67 68 #define CLAY_PADDING_ALL(padding) CLAY__CONFIG_WRAPPER(Clay_Padding, { padding, padding, padding, padding }) 69 70 #define CLAY_SIZING_FIT(...) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { __VA_ARGS__ } }, .type = CLAY__SIZING_TYPE_FIT }) 71 72 #define CLAY_SIZING_GROW(...) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { __VA_ARGS__ } }, .type = CLAY__SIZING_TYPE_GROW }) 73 74 #define CLAY_SIZING_FIXED(fixedSize) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { fixedSize, fixedSize } }, .type = CLAY__SIZING_TYPE_FIXED }) 75 76 #define CLAY_SIZING_PERCENT(percentOfParent) (CLAY__INIT(Clay_SizingAxis) { .size = { .percent = (percentOfParent) }, .type = CLAY__SIZING_TYPE_PERCENT }) 77 78 // Note: If a compile error led you here, you might be trying to use CLAY_ID with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID instead. 79 #define CLAY_ID(label) CLAY_SID(CLAY_STRING(label)) 80 81 #define CLAY_SID(label) Clay__HashString(label, 0) 82 83 // Note: If a compile error led you here, you might be trying to use CLAY_IDI with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI instead. 84 #define CLAY_IDI(label, index) CLAY_SIDI(CLAY_STRING(label), index) 85 86 #define CLAY_SIDI(label, index) Clay__HashStringWithOffset(label, index, 0) 87 88 // 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. 89 #define CLAY_ID_LOCAL(label) CLAY_SID_LOCAL(CLAY_STRING(label)) 90 91 #define CLAY_SID_LOCAL(label) Clay__HashString(label, Clay_GetOpenElementId()) 92 93 // 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. 94 #define CLAY_IDI_LOCAL(label, index) CLAY_SIDI_LOCAL(CLAY_STRING(label), index) 95 96 #define CLAY_SIDI_LOCAL(label, index) Clay__HashStringWithOffset(label, index, Clay_GetOpenElementId()) 97 98 #define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0])) 99 100 #define CLAY__ENSURE_STRING_LITERAL(x) ("" x "") 101 102 // Note: If an error led you here, it's because CLAY_STRING can only be used with string literals, i.e. CLAY_STRING("SomeString") and not CLAY_STRING(yourString) 103 #define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }) 104 105 #define CLAY_STRING_CONST(string) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) } 106 107 static uint8_t CLAY__ELEMENT_DEFINITION_LATCH; 108 109 // GCC marks the above CLAY__ELEMENT_DEFINITION_LATCH as an unused variable for files that include clay.h but don't declare any layout 110 // This is to suppress that warning 111 static inline void Clay__SuppressUnusedLatchDefinitionVariableWarning(void) { (void) CLAY__ELEMENT_DEFINITION_LATCH; } 112 113 // Publicly visible layout element macros ----------------------------------------------------- 114 115 /* This macro looks scary on the surface, but is actually quite simple. 116 It turns a macro call like this: 117 118 CLAY({ 119 .id = CLAY_ID("Container"), 120 .backgroundColor = { 255, 200, 200, 255 } 121 }) { 122 ...children declared here 123 } 124 125 Into calls like this: 126 127 Clay__OpenElement(); 128 Clay__ConfigureOpenElement((Clay_ElementDeclaration) { 129 .id = CLAY_ID("Container"), 130 .backgroundColor = { 255, 200, 200, 255 } 131 }); 132 ...children declared here 133 Clay__CloseElement(); 134 135 The for loop will only ever run a single iteration, putting Clay__CloseElement() in the increment of the loop 136 means that it will run after the body - where the children are declared. It just exists to make sure you don't forget 137 to call Clay_CloseElement(). 138 */ 139 #define CLAY_AUTO_ID(...) \ 140 for ( \ 141 CLAY__ELEMENT_DEFINITION_LATCH = (Clay__OpenElement(), Clay__ConfigureOpenElement(CLAY__CONFIG_WRAPPER(Clay_ElementDeclaration, __VA_ARGS__)), 0); \ 142 CLAY__ELEMENT_DEFINITION_LATCH < 1; \ 143 CLAY__ELEMENT_DEFINITION_LATCH=1, Clay__CloseElement() \ 144 ) 145 146 #define CLAY(id, ...) \ 147 for ( \ 148 CLAY__ELEMENT_DEFINITION_LATCH = (Clay__OpenElementWithId(id), Clay__ConfigureOpenElement(CLAY__CONFIG_WRAPPER(Clay_ElementDeclaration, __VA_ARGS__)), 0); \ 149 CLAY__ELEMENT_DEFINITION_LATCH < 1; \ 150 CLAY__ELEMENT_DEFINITION_LATCH=1, Clay__CloseElement() \ 151 ) 152 153 // These macros exist to allow the CLAY() macro to be called both with an inline struct definition, such as 154 // CLAY({ .id = something... }); 155 // As well as by passing a predefined declaration struct 156 // Clay_ElementDeclaration declarationStruct = ... 157 // CLAY(declarationStruct); 158 #define CLAY__WRAPPER_TYPE(type) Clay__##type##Wrapper 159 #define CLAY__WRAPPER_STRUCT(type) typedef struct { type wrapped; } CLAY__WRAPPER_TYPE(type) 160 #define CLAY__CONFIG_WRAPPER(type, ...) (CLAY__INIT(CLAY__WRAPPER_TYPE(type)) { __VA_ARGS__ }).wrapped 161 162 #define CLAY_TEXT(text, ...) Clay__OpenTextElement(text, CLAY__CONFIG_WRAPPER(Clay_TextElementConfig, __VA_ARGS__)) 163 164 #ifdef __cplusplus 165 166 #define CLAY__INIT(type) type 167 168 #define CLAY_PACKED_ENUM enum : uint8_t 169 170 #define CLAY__DEFAULT_STRUCT {} 171 172 #else 173 174 #define CLAY__INIT(type) (type) 175 176 #if defined(_MSC_VER) && !defined(__clang__) 177 #define CLAY_PACKED_ENUM __pragma(pack(push, 1)) enum __pragma(pack(pop)) 178 #else 179 #define CLAY_PACKED_ENUM enum __attribute__((__packed__)) 180 #endif 181 182 #if __STDC_VERSION__ >= 202311L 183 #define CLAY__DEFAULT_STRUCT {} 184 #else 185 #define CLAY__DEFAULT_STRUCT {0} 186 #endif 187 188 #endif // __cplusplus 189 190 #ifdef __cplusplus 191 extern "C" { 192 #endif 193 194 // Utility Structs ------------------------- 195 196 // Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string, 197 // but it is also used to represent slices. 198 typedef struct Clay_String { 199 // Set this boolean to true if the char* data underlying this string will live for the entire lifetime of the program. 200 // This will automatically be set for strings created with CLAY_STRING, as the macro requires a string literal. 201 bool isStaticallyAllocated; 202 int32_t length; 203 // The underlying character memory. Note: this will not be copied and will not extend the lifetime of the underlying memory. 204 const char *chars; 205 } Clay_String; 206 207 // Clay_StringSlice is used to represent non owning string slices, and includes 208 // a baseChars field which points to the string this slice is derived from. 209 typedef struct Clay_StringSlice { 210 int32_t length; 211 const char *chars; 212 const char *baseChars; // The source string / char* that this slice was derived from 213 } Clay_StringSlice; 214 215 typedef struct Clay_Context Clay_Context; 216 217 // Clay_Arena is a memory arena structure that is used by clay to manage its internal allocations. 218 // Rather than creating it by hand, it's easier to use Clay_CreateArenaWithCapacityAndMemory() 219 typedef struct Clay_Arena { 220 uintptr_t nextAllocation; 221 size_t capacity; 222 char *memory; 223 } Clay_Arena; 224 225 typedef struct Clay_Dimensions { 226 float width, height; 227 } Clay_Dimensions; 228 229 typedef struct Clay_Vector2 { 230 float x, y; 231 } Clay_Vector2; 232 233 // Internally clay conventionally represents colors as 0-255, but interpretation is up to the renderer. 234 typedef struct Clay_Color { 235 float r, g, b, a; 236 } Clay_Color; 237 238 typedef struct Clay_BoundingBox { 239 float x, y, width, height; 240 } Clay_BoundingBox; 241 242 // Primarily created via the CLAY_ID(), CLAY_IDI(), CLAY_ID_LOCAL() and CLAY_IDI_LOCAL() macros. 243 // Represents a hashed string ID used for identifying and finding specific clay UI elements, required 244 // by functions such as Clay_PointerOver() and Clay_GetElementData(). 245 typedef struct Clay_ElementId { 246 uint32_t id; // The resulting hash generated from the other fields. 247 uint32_t offset; // A numerical offset applied after computing the hash from stringId. 248 uint32_t baseId; // A base hash value to start from, for example the parent element ID is used when calculating CLAY_ID_LOCAL(). 249 Clay_String stringId; // The string id to hash. 250 } Clay_ElementId; 251 252 // A sized array of Clay_ElementId. 253 typedef struct 254 { 255 int32_t capacity; 256 int32_t length; 257 Clay_ElementId *internalArray; 258 } Clay_ElementIdArray; 259 260 // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. 261 // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. 262 typedef struct Clay_CornerRadius { 263 float topLeft; 264 float topRight; 265 float bottomLeft; 266 float bottomRight; 267 } Clay_CornerRadius; 268 269 // Element Configs --------------------------- 270 271 // Controls the direction in which child elements will be automatically laid out. 272 typedef CLAY_PACKED_ENUM { 273 // (Default) Lays out child elements from left to right with increasing x. 274 CLAY_LEFT_TO_RIGHT, 275 // Lays out child elements from top to bottom with increasing y. 276 CLAY_TOP_TO_BOTTOM, 277 } Clay_LayoutDirection; 278 279 // Controls the alignment along the x axis (horizontal) of child elements. 280 typedef CLAY_PACKED_ENUM { 281 // (Default) Aligns child elements to the left hand side of this element, offset by padding.width.left 282 CLAY_ALIGN_X_LEFT, 283 // Aligns child elements to the right hand side of this element, offset by padding.width.right 284 CLAY_ALIGN_X_RIGHT, 285 // Aligns child elements horizontally to the center of this element 286 CLAY_ALIGN_X_CENTER, 287 } Clay_LayoutAlignmentX; 288 289 // Controls the alignment along the y axis (vertical) of child elements. 290 typedef CLAY_PACKED_ENUM { 291 // (Default) Aligns child elements to the top of this element, offset by padding.width.top 292 CLAY_ALIGN_Y_TOP, 293 // Aligns child elements to the bottom of this element, offset by padding.width.bottom 294 CLAY_ALIGN_Y_BOTTOM, 295 // Aligns child elements vertically to the center of this element 296 CLAY_ALIGN_Y_CENTER, 297 } Clay_LayoutAlignmentY; 298 299 // Controls how the element takes up space inside its parent container. 300 typedef CLAY_PACKED_ENUM { 301 // (default) Wraps tightly to the size of the element's contents. 302 CLAY__SIZING_TYPE_FIT, 303 // Expands along this axis to fill available space in the parent element, sharing it with other GROW elements. 304 CLAY__SIZING_TYPE_GROW, 305 // Expects 0-1 range. Clamps the axis size to a percent of the parent container's axis size minus padding and child gaps. 306 CLAY__SIZING_TYPE_PERCENT, 307 // Clamps the axis size to an exact size in pixels. 308 CLAY__SIZING_TYPE_FIXED, 309 } Clay__SizingType; 310 311 // Controls how child elements are aligned on each axis. 312 typedef struct Clay_ChildAlignment { 313 Clay_LayoutAlignmentX x; // Controls alignment of children along the x axis. 314 Clay_LayoutAlignmentY y; // Controls alignment of children along the y axis. 315 } Clay_ChildAlignment; 316 317 // Controls the minimum and maximum size in pixels that this element is allowed to grow or shrink to, 318 // overriding sizing types such as FIT or GROW. 319 typedef struct Clay_SizingMinMax { 320 float min; // The smallest final size of the element on this axis will be this value in pixels. 321 float max; // The largest final size of the element on this axis will be this value in pixels. 322 } Clay_SizingMinMax; 323 324 // Controls the sizing of this element along one axis inside its parent container. 325 typedef struct Clay_SizingAxis { 326 union { 327 Clay_SizingMinMax minMax; // Controls the minimum and maximum size in pixels that this element is allowed to grow or shrink to, overriding sizing types such as FIT or GROW. 328 float percent; // Expects 0-1 range. Clamps the axis size to a percent of the parent container's axis size minus padding and child gaps. 329 } size; 330 Clay__SizingType type; // Controls how the element takes up space inside its parent container. 331 } Clay_SizingAxis; 332 333 // Controls the sizing of this element along one axis inside its parent container. 334 typedef struct Clay_Sizing { 335 Clay_SizingAxis width; // Controls the width sizing of the element, along the x axis. 336 Clay_SizingAxis height; // Controls the height sizing of the element, along the y axis. 337 } Clay_Sizing; 338 339 // Controls "padding" in pixels, which is a gap between the bounding box of this element and where its children 340 // will be placed. 341 typedef struct Clay_Padding { 342 uint16_t left; 343 uint16_t right; 344 uint16_t top; 345 uint16_t bottom; 346 } Clay_Padding; 347 348 CLAY__WRAPPER_STRUCT(Clay_Padding); 349 350 // Controls various settings that affect the size and position of an element, as well as the sizes and positions 351 // of any child elements. 352 typedef struct Clay_LayoutConfig { 353 Clay_Sizing sizing; // Controls the sizing of this element inside it's parent container, including FIT, GROW, PERCENT and FIXED sizing. 354 Clay_Padding padding; // Controls "padding" in pixels, which is a gap between the bounding box of this element and where its children will be placed. 355 uint16_t childGap; // Controls the gap in pixels between child elements along the layout axis (horizontal gap for LEFT_TO_RIGHT, vertical gap for TOP_TO_BOTTOM). 356 Clay_ChildAlignment childAlignment; // Controls how child elements are aligned on each axis. 357 Clay_LayoutDirection layoutDirection; // Controls the direction in which child elements will be automatically laid out. 358 } Clay_LayoutConfig; 359 360 CLAY__WRAPPER_STRUCT(Clay_LayoutConfig); 361 362 extern Clay_LayoutConfig CLAY_LAYOUT_DEFAULT; 363 364 // Controls how text "wraps", that is how it is broken into multiple lines when there is insufficient horizontal space. 365 typedef CLAY_PACKED_ENUM { 366 // (default) breaks on whitespace characters. 367 CLAY_TEXT_WRAP_WORDS, 368 // Don't break on space characters, only on newlines. 369 CLAY_TEXT_WRAP_NEWLINES, 370 // Disable text wrapping entirely. 371 CLAY_TEXT_WRAP_NONE, 372 } Clay_TextElementConfigWrapMode; 373 374 // Controls how wrapped lines of text are horizontally aligned within the outer text bounding box. 375 typedef CLAY_PACKED_ENUM { 376 // (default) Horizontally aligns wrapped lines of text to the left hand side of their bounding box. 377 CLAY_TEXT_ALIGN_LEFT, 378 // Horizontally aligns wrapped lines of text to the center of their bounding box. 379 CLAY_TEXT_ALIGN_CENTER, 380 // Horizontally aligns wrapped lines of text to the right hand side of their bounding box. 381 CLAY_TEXT_ALIGN_RIGHT, 382 } Clay_TextAlignment; 383 384 // Controls various functionality related to text elements. 385 typedef struct Clay_TextElementConfig { 386 // A pointer that will be transparently passed through to the resulting render command. 387 void *userData; 388 // The RGBA color of the font to render, conventionally specified as 0-255. 389 Clay_Color textColor; 390 // An integer transparently passed to Clay_MeasureText to identify the font to use. 391 // The debug view will pass fontId = 0 for its internal text. 392 uint16_t fontId; 393 // Controls the size of the font. Handled by the function provided to Clay_MeasureText. 394 uint16_t fontSize; 395 // Controls extra horizontal spacing between characters. Handled by the function provided to Clay_MeasureText. 396 uint16_t letterSpacing; 397 // Controls additional vertical space between wrapped lines of text. 398 uint16_t lineHeight; 399 // Controls how text "wraps", that is how it is broken into multiple lines when there is insufficient horizontal space. 400 // CLAY_TEXT_WRAP_WORDS (default) breaks on whitespace characters. 401 // CLAY_TEXT_WRAP_NEWLINES doesn't break on space characters, only on newlines. 402 // CLAY_TEXT_WRAP_NONE disables wrapping entirely. 403 Clay_TextElementConfigWrapMode wrapMode; 404 // Controls how wrapped lines of text are horizontally aligned within the outer text bounding box. 405 // CLAY_TEXT_ALIGN_LEFT (default) - Horizontally aligns wrapped lines of text to the left hand side of their bounding box. 406 // CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box. 407 // CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box. 408 Clay_TextAlignment textAlignment; 409 } Clay_TextElementConfig; 410 411 CLAY__WRAPPER_STRUCT(Clay_TextElementConfig); 412 413 // Aspect Ratio -------------------------------- 414 415 // Controls various settings related to aspect ratio scaling element. 416 typedef struct Clay_AspectRatioElementConfig { 417 float aspectRatio; // A float representing the target "Aspect ratio" for an element, which is its final width divided by its final height. 418 } Clay_AspectRatioElementConfig; 419 420 CLAY__WRAPPER_STRUCT(Clay_AspectRatioElementConfig); 421 422 // Image -------------------------------- 423 424 // Controls various settings related to image elements. 425 typedef struct Clay_ImageElementConfig { 426 void* imageData; // A transparent pointer used to pass image data through to the renderer. 427 } Clay_ImageElementConfig; 428 429 CLAY__WRAPPER_STRUCT(Clay_ImageElementConfig); 430 431 // Floating ----------------------------- 432 433 // Controls where a floating element is offset relative to its parent element. 434 // Note: see https://github.com/user-attachments/assets/b8c6dfaa-c1b1-41a4-be55-013473e4a6ce for a visual explanation. 435 typedef CLAY_PACKED_ENUM { 436 CLAY_ATTACH_POINT_LEFT_TOP, 437 CLAY_ATTACH_POINT_LEFT_CENTER, 438 CLAY_ATTACH_POINT_LEFT_BOTTOM, 439 CLAY_ATTACH_POINT_CENTER_TOP, 440 CLAY_ATTACH_POINT_CENTER_CENTER, 441 CLAY_ATTACH_POINT_CENTER_BOTTOM, 442 CLAY_ATTACH_POINT_RIGHT_TOP, 443 CLAY_ATTACH_POINT_RIGHT_CENTER, 444 CLAY_ATTACH_POINT_RIGHT_BOTTOM, 445 } Clay_FloatingAttachPointType; 446 447 // Controls where a floating element is offset relative to its parent element. 448 typedef struct Clay_FloatingAttachPoints { 449 Clay_FloatingAttachPointType element; // Controls the origin point on a floating element that attaches to its parent. 450 Clay_FloatingAttachPointType parent; // Controls the origin point on the parent element that the floating element attaches to. 451 } Clay_FloatingAttachPoints; 452 453 // Controls how mouse pointer events like hover and click are captured or passed through to elements underneath a floating element. 454 typedef CLAY_PACKED_ENUM { 455 // (default) "Capture" the pointer event and don't allow events like hover and click to pass through to elements underneath. 456 CLAY_POINTER_CAPTURE_MODE_CAPTURE, 457 // CLAY_POINTER_CAPTURE_MODE_PARENT, TODO pass pointer through to attached parent 458 // Transparently pass through pointer events like hover and click to elements underneath the floating element. 459 CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, 460 } Clay_PointerCaptureMode; 461 462 // Controls which element a floating element is "attached" to (i.e. relative offset from). 463 typedef CLAY_PACKED_ENUM { 464 // (default) Disables floating for this element. 465 CLAY_ATTACH_TO_NONE, 466 // Attaches this floating element to its parent, positioned based on the .attachPoints and .offset fields. 467 CLAY_ATTACH_TO_PARENT, 468 // Attaches this floating element to an element with a specific ID, specified with the .parentId field. positioned based on the .attachPoints and .offset fields. 469 CLAY_ATTACH_TO_ELEMENT_WITH_ID, 470 // Attaches this floating element to the root of the layout, which combined with the .offset field provides functionality similar to "absolute positioning". 471 CLAY_ATTACH_TO_ROOT, 472 } Clay_FloatingAttachToElement; 473 474 // Controls whether or not a floating element is clipped to the same clipping rectangle as the element it's attached to. 475 typedef CLAY_PACKED_ENUM { 476 // (default) - The floating element does not inherit clipping. 477 CLAY_CLIP_TO_NONE, 478 // The floating element is clipped to the same clipping rectangle as the element it's attached to. 479 CLAY_CLIP_TO_ATTACHED_PARENT 480 } Clay_FloatingClipToElement; 481 482 // Controls various settings related to "floating" elements, which are elements that "float" above other elements, potentially overlapping their boundaries, 483 // and not affecting the layout of sibling or parent elements. 484 typedef struct Clay_FloatingElementConfig { 485 // Offsets this floating element by the provided x,y coordinates from its attachPoints. 486 Clay_Vector2 offset; 487 // Expands the boundaries of the outer floating element without affecting its children. 488 Clay_Dimensions expand; 489 // When used in conjunction with .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, attaches this floating element to the element in the hierarchy with the provided ID. 490 // Hint: attach the ID to the other element with .id = CLAY_ID("yourId"), and specify the id the same way, with .parentId = CLAY_ID("yourId").id 491 uint32_t parentId; 492 // Controls the z index of this floating element and all its children. Floating elements are sorted in ascending z order before output. 493 // zIndex is also passed to the renderer for all elements contained within this floating element. 494 int16_t zIndex; 495 // Controls how mouse pointer events like hover and click are captured or passed through to elements underneath / behind a floating element. 496 // Enum is of the form CLAY_ATTACH_POINT_foo_bar. See Clay_FloatingAttachPoints for more details. 497 // Note: see <img src="https://github.com/user-attachments/assets/b8c6dfaa-c1b1-41a4-be55-013473e4a6ce /> 498 // and <img src="https://github.com/user-attachments/assets/ebe75e0d-1904-46b0-982d-418f929d1516 /> for a visual explanation. 499 Clay_FloatingAttachPoints attachPoints; 500 // Controls how mouse pointer events like hover and click are captured or passed through to elements underneath a floating element. 501 // CLAY_POINTER_CAPTURE_MODE_CAPTURE (default) - "Capture" the pointer event and don't allow events like hover and click to pass through to elements underneath. 502 // CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH - Transparently pass through pointer events like hover and click to elements underneath the floating element. 503 Clay_PointerCaptureMode pointerCaptureMode; 504 // Controls which element a floating element is "attached" to (i.e. relative offset from). 505 // CLAY_ATTACH_TO_NONE (default) - Disables floating for this element. 506 // CLAY_ATTACH_TO_PARENT - Attaches this floating element to its parent, positioned based on the .attachPoints and .offset fields. 507 // CLAY_ATTACH_TO_ELEMENT_WITH_ID - Attaches this floating element to an element with a specific ID, specified with the .parentId field. positioned based on the .attachPoints and .offset fields. 508 // CLAY_ATTACH_TO_ROOT - Attaches this floating element to the root of the layout, which combined with the .offset field provides functionality similar to "absolute positioning". 509 Clay_FloatingAttachToElement attachTo; 510 // Controls whether or not a floating element is clipped to the same clipping rectangle as the element it's attached to. 511 // CLAY_CLIP_TO_NONE (default) - The floating element does not inherit clipping. 512 // CLAY_CLIP_TO_ATTACHED_PARENT - The floating element is clipped to the same clipping rectangle as the element it's attached to. 513 Clay_FloatingClipToElement clipTo; 514 } Clay_FloatingElementConfig; 515 516 CLAY__WRAPPER_STRUCT(Clay_FloatingElementConfig); 517 518 // Custom ----------------------------- 519 520 // Controls various settings related to custom elements. 521 typedef struct Clay_CustomElementConfig { 522 // A transparent pointer through which you can pass custom data to the renderer. 523 // Generates CUSTOM render commands. 524 void* customData; 525 } Clay_CustomElementConfig; 526 527 CLAY__WRAPPER_STRUCT(Clay_CustomElementConfig); 528 529 // Scroll ----------------------------- 530 531 // Controls the axis on which an element switches to "scrolling", which clips the contents and allows scrolling in that direction. 532 typedef struct Clay_ClipElementConfig { 533 bool horizontal; // Clip overflowing elements on the X axis. 534 bool vertical; // Clip overflowing elements on the Y axis. 535 Clay_Vector2 childOffset; // Offsets the x,y positions of all child elements. Used primarily for scrolling containers. 536 } Clay_ClipElementConfig; 537 538 CLAY__WRAPPER_STRUCT(Clay_ClipElementConfig); 539 540 // Border ----------------------------- 541 542 // Controls the widths of individual element borders. 543 typedef struct Clay_BorderWidth { 544 uint16_t left; 545 uint16_t right; 546 uint16_t top; 547 uint16_t bottom; 548 // Creates borders between each child element, depending on the .layoutDirection. 549 // e.g. for LEFT_TO_RIGHT, borders will be vertical lines, and for TOP_TO_BOTTOM borders will be horizontal lines. 550 // .betweenChildren borders will result in individual RECTANGLE render commands being generated. 551 uint16_t betweenChildren; 552 } Clay_BorderWidth; 553 554 // Controls settings related to element borders. 555 typedef struct Clay_BorderElementConfig { 556 Clay_Color color; // Controls the color of all borders with width > 0. Conventionally represented as 0-255, but interpretation is up to the renderer. 557 Clay_BorderWidth width; // Controls the widths of individual borders. At least one of these should be > 0 for a BORDER render command to be generated. 558 } Clay_BorderElementConfig; 559 560 CLAY__WRAPPER_STRUCT(Clay_BorderElementConfig); 561 562 typedef struct { 563 Clay_BoundingBox boundingBox; 564 Clay_Color backgroundColor; 565 Clay_Color overlayColor; 566 Clay_Color borderColor; 567 Clay_BorderWidth borderWidth; 568 } Clay_TransitionData; 569 570 typedef enum { 571 CLAY_TRANSITION_STATE_IDLE, 572 CLAY_TRANSITION_STATE_ENTERING, 573 CLAY_TRANSITION_STATE_TRANSITIONING, 574 CLAY_TRANSITION_STATE_EXITING, 575 } Clay_TransitionState; 576 577 typedef enum { 578 CLAY_TRANSITION_PROPERTY_NONE = 0, 579 CLAY_TRANSITION_PROPERTY_X = 1, 580 CLAY_TRANSITION_PROPERTY_Y = 2, 581 CLAY_TRANSITION_PROPERTY_POSITION = CLAY_TRANSITION_PROPERTY_X | CLAY_TRANSITION_PROPERTY_Y, 582 CLAY_TRANSITION_PROPERTY_WIDTH = 4, 583 CLAY_TRANSITION_PROPERTY_HEIGHT = 8, 584 CLAY_TRANSITION_PROPERTY_DIMENSIONS = CLAY_TRANSITION_PROPERTY_WIDTH | CLAY_TRANSITION_PROPERTY_HEIGHT, 585 CLAY_TRANSITION_PROPERTY_BOUNDING_BOX = CLAY_TRANSITION_PROPERTY_POSITION | CLAY_TRANSITION_PROPERTY_DIMENSIONS, 586 CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR = 16, 587 CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR = 32, 588 CLAY_TRANSITION_PROPERTY_CORNER_RADIUS = 64, 589 CLAY_TRANSITION_PROPERTY_BORDER_COLOR = 128, 590 CLAY_TRANSITION_PROPERTY_BORDER_WIDTH = 256, 591 CLAY_TRANSITION_PROPERTY_BORDER = CLAY_TRANSITION_PROPERTY_BORDER_COLOR | CLAY_TRANSITION_PROPERTY_BORDER_WIDTH 592 } Clay_TransitionProperty; 593 594 typedef struct { 595 Clay_TransitionState transitionState; 596 Clay_TransitionData initial; 597 Clay_TransitionData *current; 598 Clay_TransitionData target; 599 float elapsedTime; 600 float duration; 601 Clay_TransitionProperty properties; 602 } Clay_TransitionCallbackArguments; 603 604 typedef CLAY_PACKED_ENUM { 605 CLAY_TRANSITION_ENTER_SKIP_ON_FIRST_PARENT_FRAME, 606 CLAY_TRANSITION_ENTER_TRIGGER_ON_FIRST_PARENT_FRAME, 607 } Clay_TransitionEnterTriggerType; 608 609 typedef CLAY_PACKED_ENUM { 610 CLAY_TRANSITION_EXIT_SKIP_WHEN_PARENT_EXITS, 611 CLAY_TRANSITION_EXIT_TRIGGER_WHEN_PARENT_EXITS, 612 } Clay_TransitionExitTriggerType; 613 614 typedef CLAY_PACKED_ENUM { 615 CLAY_TRANSITION_DISABLE_INTERACTIONS_WHILE_TRANSITIONING_POSITION, 616 CLAY_TRANSITION_ALLOW_INTERACTIONS_WHILE_TRANSITIONING_POSITION, 617 } Clay_TransitionInteractionHandlingType; 618 619 typedef CLAY_PACKED_ENUM { 620 CLAY_EXIT_TRANSITION_ORDERING_UNDERNEATH_SIBLINGS, 621 CLAY_EXIT_TRANSITION_ORDERING_NATURAL_ORDER, 622 CLAY_EXIT_TRANSITION_ORDERING_ABOVE_SIBLINGS, 623 } Clay_ExitTransitionSiblingOrdering; 624 625 // Controls settings related to transitions 626 typedef struct Clay_TransitionElementConfig { 627 bool (*handler)(Clay_TransitionCallbackArguments arguments); 628 float duration; 629 Clay_TransitionProperty properties; 630 Clay_TransitionInteractionHandlingType interactionHandling; 631 struct { 632 Clay_TransitionData (*setInitialState)(Clay_TransitionData targetState, Clay_TransitionProperty properties); 633 Clay_TransitionEnterTriggerType trigger; 634 } enter; 635 struct { 636 Clay_TransitionData (*setFinalState)(Clay_TransitionData initialState, Clay_TransitionProperty properties); 637 Clay_TransitionExitTriggerType trigger; 638 Clay_ExitTransitionSiblingOrdering siblingOrdering; 639 } exit; 640 } Clay_TransitionElementConfig; 641 642 CLAY__WRAPPER_STRUCT(Clay_TransitionElementConfig); 643 644 // Render Command Data ----------------------------- 645 646 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_TEXT 647 typedef struct Clay_TextRenderData { 648 // A string slice containing the text to be rendered. 649 // Note: this is not guaranteed to be null terminated. 650 Clay_StringSlice stringContents; 651 // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. 652 Clay_Color textColor; 653 // An integer representing the font to use to render this text, transparently passed through from the text declaration. 654 uint16_t fontId; 655 uint16_t fontSize; 656 // Specifies the extra whitespace gap in pixels between each character. 657 uint16_t letterSpacing; 658 // The height of the bounding box for this line of text. 659 uint16_t lineHeight; 660 } Clay_TextRenderData; 661 662 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE 663 typedef struct Clay_RectangleRenderData { 664 // The solid background color to fill this rectangle with. Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. 665 Clay_Color backgroundColor; 666 // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. 667 // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. 668 Clay_CornerRadius cornerRadius; 669 } Clay_RectangleRenderData; 670 671 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE 672 typedef struct Clay_ImageRenderData { 673 // The tint color for this image. Note that the default value is 0,0,0,0 and should likely be interpreted 674 // as "untinted". 675 // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. 676 Clay_Color backgroundColor; 677 // Controls the "radius", or corner rounding of this image. 678 // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. 679 Clay_CornerRadius cornerRadius; 680 // A pointer transparently passed through from the original element definition, typically used to represent image data. 681 void* imageData; 682 } Clay_ImageRenderData; 683 684 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM 685 typedef struct Clay_CustomRenderData { 686 // Passed through from .backgroundColor in the original element declaration. 687 // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. 688 Clay_Color backgroundColor; 689 // Controls the "radius", or corner rounding of this custom element. 690 // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. 691 Clay_CornerRadius cornerRadius; 692 // A pointer transparently passed through from the original element definition. 693 void* customData; 694 } Clay_CustomRenderData; 695 696 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START || commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_END 697 typedef struct Clay_ClipRenderData { 698 bool horizontal; 699 bool vertical; 700 } Clay_ClipRenderData; 701 702 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START || commandType == CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_END 703 typedef struct Clay_OverlayColorRenderData { 704 Clay_Color color; 705 } Clay_OverlayColorRenderData; 706 707 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_BORDER 708 typedef struct Clay_BorderRenderData { 709 // Controls a shared color for all this element's borders. 710 // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. 711 Clay_Color color; 712 // Specifies the "radius", or corner rounding of this border element. 713 // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. 714 Clay_CornerRadius cornerRadius; 715 // Controls individual border side widths. 716 Clay_BorderWidth width; 717 } Clay_BorderRenderData; 718 719 // A struct union containing data specific to this command's .commandType 720 typedef union Clay_RenderData { 721 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE 722 Clay_RectangleRenderData rectangle; 723 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_TEXT 724 Clay_TextRenderData text; 725 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE 726 Clay_ImageRenderData image; 727 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM 728 Clay_CustomRenderData custom; 729 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_BORDER 730 Clay_BorderRenderData border; 731 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START|END 732 Clay_ClipRenderData clip; 733 // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START|END 734 Clay_OverlayColorRenderData overlayColor; 735 } Clay_RenderData; 736 737 // Miscellaneous Structs & Enums --------------------------------- 738 739 // Data representing the current internal state of a scrolling element. 740 typedef struct Clay_ScrollContainerData { 741 // Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. 742 // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling. 743 Clay_Vector2 *scrollPosition; 744 // The bounding box of the scroll element. 745 Clay_Dimensions scrollContainerDimensions; 746 // The outer dimensions of the inner scroll container content, including the padding of the parent scroll container. 747 Clay_Dimensions contentDimensions; 748 // The config that was originally passed to the clip element. 749 Clay_ClipElementConfig config; 750 // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. 751 bool found; 752 } Clay_ScrollContainerData; 753 754 // Bounding box and other data for a specific UI element. 755 typedef struct Clay_ElementData { 756 // The rectangle that encloses this UI element, with the position relative to the root of the layout. 757 Clay_BoundingBox boundingBox; 758 // Indicates whether an actual Element matched the provided ID or if the default struct was returned. 759 bool found; 760 } Clay_ElementData; 761 762 // Used by renderers to determine specific handling for each render command. 763 typedef CLAY_PACKED_ENUM { 764 // This command type should be skipped. 765 CLAY_RENDER_COMMAND_TYPE_NONE, 766 // The renderer should draw a solid color rectangle. 767 CLAY_RENDER_COMMAND_TYPE_RECTANGLE, 768 // The renderer should draw a colored border inset into the bounding box. 769 CLAY_RENDER_COMMAND_TYPE_BORDER, 770 // The renderer should draw text. 771 CLAY_RENDER_COMMAND_TYPE_TEXT, 772 // The renderer should draw an image. 773 CLAY_RENDER_COMMAND_TYPE_IMAGE, 774 // The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox. 775 CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, 776 // The renderer should finish any previously active clipping, and begin rendering elements in full again. 777 CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, 778 // The renderer should begin performing a "color overlay" on all subsequent render commands until disabled again. 779 CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START, 780 // The renderer should disable any previously active "color overlay" and render elements with their standard colors again. 781 CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_END, 782 // The renderer should provide a custom implementation for handling this render command based on its .customData 783 CLAY_RENDER_COMMAND_TYPE_CUSTOM, 784 } Clay_RenderCommandType; 785 786 typedef struct Clay_RenderCommand { 787 // A rectangular box that fully encloses this UI element, with the position relative to the root of the layout. 788 Clay_BoundingBox boundingBox; 789 // A struct union containing data specific to this command's commandType. 790 Clay_RenderData renderData; 791 // A pointer transparently passed through from the original element declaration. 792 void *userData; 793 // The id of this element, transparently passed through from the original element declaration. 794 uint32_t id; 795 // The z order required for drawing this command correctly. 796 // Note: the render command array is already sorted in ascending order, and will produce correct results if drawn in naive order. 797 // This field is intended for use in batching renderers for improved performance. 798 int16_t zIndex; 799 // Specifies how to handle rendering of this command. 800 // CLAY_RENDER_COMMAND_TYPE_RECTANGLE - The renderer should draw a solid color rectangle. 801 // CLAY_RENDER_COMMAND_TYPE_BORDER - The renderer should draw a colored border inset into the bounding box. 802 // CLAY_RENDER_COMMAND_TYPE_TEXT - The renderer should draw text. 803 // CLAY_RENDER_COMMAND_TYPE_IMAGE - The renderer should draw an image. 804 // CLAY_RENDER_COMMAND_TYPE_SCISSOR_START - The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox. 805 // CLAY_RENDER_COMMAND_TYPE_SCISSOR_END - The renderer should finish any previously active clipping, and begin rendering elements in full again. 806 // CLAY_RENDER_COMMAND_TYPE_CUSTOM - The renderer should provide a custom implementation for handling this render command based on its .customData 807 Clay_RenderCommandType commandType; 808 } Clay_RenderCommand; 809 810 // A sized array of render commands. 811 typedef struct Clay_RenderCommandArray { 812 // The underlying max capacity of the array, not necessarily all initialized. 813 int32_t capacity; 814 // The number of initialized elements in this array. Used for loops and iteration. 815 int32_t length; 816 // A pointer to the first element in the internal array. 817 Clay_RenderCommand* internalArray; 818 } Clay_RenderCommandArray; 819 820 // Represents the current state of interaction with clay this frame. 821 typedef CLAY_PACKED_ENUM { 822 // A left mouse click, or touch occurred this frame. 823 CLAY_POINTER_DATA_PRESSED_THIS_FRAME, 824 // The left mouse button click or touch happened at some point in the past, and is still currently held down this frame. 825 CLAY_POINTER_DATA_PRESSED, 826 // The left mouse button click or touch was released this frame. 827 CLAY_POINTER_DATA_RELEASED_THIS_FRAME, 828 // The left mouse button click or touch is not currently down / was released at some point in the past. 829 CLAY_POINTER_DATA_RELEASED, 830 } Clay_PointerDataInteractionState; 831 832 // Information on the current state of pointer interactions this frame. 833 typedef struct Clay_PointerData { 834 // The position of the mouse / touch / pointer relative to the root of the layout. 835 Clay_Vector2 position; 836 // Represents the current state of interaction with clay this frame. 837 // CLAY_POINTER_DATA_PRESSED_THIS_FRAME - A left mouse click, or touch occurred this frame. 838 // CLAY_POINTER_DATA_PRESSED - The left mouse button click or touch happened at some point in the past, and is still currently held down this frame. 839 // CLAY_POINTER_DATA_RELEASED_THIS_FRAME - The left mouse button click or touch was released this frame. 840 // CLAY_POINTER_DATA_RELEASED - The left mouse button click or touch is not currently down / was released at some point in the past. 841 Clay_PointerDataInteractionState state; 842 } Clay_PointerData; 843 844 typedef struct Clay_ElementDeclaration { 845 // Controls various settings that affect the size and position of an element, as well as the sizes and positions of any child elements. 846 Clay_LayoutConfig layout; 847 // Controls the background color of the resulting element. 848 // By convention specified as 0-255, but interpretation is up to the renderer. 849 // 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. 850 Clay_Color backgroundColor; 851 // Perform an image editing style "Color Overlay" on this element and all its children, equivalent to 852 // glsl mix(elementColor, overlayColor.rgb, overlayColor.a) 853 Clay_Color overlayColor; 854 // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. 855 Clay_CornerRadius cornerRadius; 856 // Controls settings related to aspect ratio scaling. 857 Clay_AspectRatioElementConfig aspectRatio; 858 // Controls settings related to image elements. 859 Clay_ImageElementConfig image; 860 // Controls whether and how an element "floats", which means it layers over the top of other elements in z order, and doesn't affect the position and size of siblings or parent elements. 861 // Note: in order to activate floating, .floating.attachTo must be set to something other than the default value. 862 Clay_FloatingElementConfig floating; 863 // Used to create CUSTOM render commands, usually to render element types not supported by Clay. 864 Clay_CustomElementConfig custom; 865 // Controls whether an element should clip its contents, as well as providing child x,y offset configuration for scrolling. 866 Clay_ClipElementConfig clip; 867 // Controls settings related to element borders, and will generate BORDER render commands. 868 Clay_BorderElementConfig border; 869 Clay_TransitionElementConfig transition; 870 // A pointer that will be transparently passed through to resulting render commands. 871 void *userData; 872 } Clay_ElementDeclaration; 873 874 CLAY__WRAPPER_STRUCT(Clay_ElementDeclaration); 875 876 // Represents the type of error clay encountered while computing layout. 877 typedef CLAY_PACKED_ENUM { 878 // A text measurement function wasn't provided using Clay_SetMeasureTextFunction(), or the provided function was null. 879 CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, 880 // Clay attempted to allocate its internal data structures but ran out of space. 881 // The arena passed to Clay_Initialize was created with a capacity smaller than that required by Clay_MinMemorySize(). 882 CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, 883 // Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxElementCount(). 884 CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, 885 // Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxMeasureTextCacheWordCount(). 886 CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, 887 // Two elements were declared with exactly the same ID within one layout. 888 CLAY_ERROR_TYPE_DUPLICATE_ID, 889 // A floating element was declared using CLAY_ATTACH_TO_ELEMENT_ID and either an invalid .parentId was provided or no element with the provided .parentId was found. 890 CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, 891 // An element was declared that using CLAY_SIZING_PERCENT but the percentage value was over 1. Percentage values are expected to be in the 0-1 range. 892 CLAY_ERROR_TYPE_PERCENTAGE_OVER_1, 893 // Clay encountered an internal error. It would be wonderful if you could report this so we can fix it! 894 CLAY_ERROR_TYPE_INTERNAL_ERROR, 895 // Clay__OpenElement was called more times than Clay__CloseElement, so there were still remaining open elements when the layout ended. 896 CLAY_ERROR_TYPE_UNBALANCED_OPEN_CLOSE, 897 } Clay_ErrorType; 898 899 // Data to identify the error that clay has encountered. 900 typedef struct Clay_ErrorData { 901 // Represents the type of error clay encountered while computing layout. 902 // CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED - A text measurement function wasn't provided using Clay_SetMeasureTextFunction(), or the provided function was null. 903 // CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED - Clay attempted to allocate its internal data structures but ran out of space. The arena passed to Clay_Initialize was created with a capacity smaller than that required by Clay_MinMemorySize(). 904 // CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED - Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxElementCount(). 905 // CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED - Clay ran out of capacity in its internal array for storing elements. This limit can be increased with Clay_SetMaxMeasureTextCacheWordCount(). 906 // CLAY_ERROR_TYPE_DUPLICATE_ID - Two elements were declared with exactly the same ID within one layout. 907 // CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND - A floating element was declared using CLAY_ATTACH_TO_ELEMENT_ID and either an invalid .parentId was provided or no element with the provided .parentId was found. 908 // CLAY_ERROR_TYPE_PERCENTAGE_OVER_1 - An element was declared that using CLAY_SIZING_PERCENT but the percentage value was over 1. Percentage values are expected to be in the 0-1 range. 909 // CLAY_ERROR_TYPE_INTERNAL_ERROR - Clay encountered an internal error. It would be wonderful if you could report this so we can fix it! 910 Clay_ErrorType errorType; 911 // A string containing human-readable error text that explains the error in more detail. 912 Clay_String errorText; 913 // A transparent pointer passed through from when the error handler was first provided. 914 void *userData; 915 } Clay_ErrorData; 916 917 // A wrapper struct around Clay's error handler function. 918 typedef struct { 919 // A user provided function to call when Clay encounters an error during layout. 920 void (*errorHandlerFunction)(Clay_ErrorData errorText); 921 // A pointer that will be transparently passed through to the error handler when it is called. 922 void *userData; 923 } Clay_ErrorHandler; 924 925 // Function Forward Declarations --------------------------------- 926 927 // Public API functions ------------------------------------------ 928 929 // Returns the size, in bytes, of the minimum amount of memory Clay requires to operate at its current settings. 930 CLAY_DLL_EXPORT uint32_t Clay_MinMemorySize(void); 931 // Creates an arena for clay to use for its internal allocations, given a certain capacity in bytes and a pointer to an allocation of at least that size. 932 // Intended to be used with Clay_MinMemorySize in the following way: 933 // uint32_t minMemoryRequired = Clay_MinMemorySize(); 934 // Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(minMemoryRequired, malloc(minMemoryRequired)); 935 CLAY_DLL_EXPORT Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory); 936 // 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, 937 // as well as for Clay_Hovered() and scroll element handling. 938 CLAY_DLL_EXPORT void Clay_SetPointerState(Clay_Vector2 position, bool pointerDown); 939 // Returns the state of the "pointer" (i.e. the mouse or touch) which was set via Clay_SetPointerState(). 940 CLAY_DLL_EXPORT Clay_PointerData Clay_GetPointerState(void); 941 // Initialize Clay's internal arena and setup required data before layout can begin. Only needs to be called once. 942 // - arena can be created using Clay_CreateArenaWithCapacityAndMemory() 943 // - layoutDimensions are the initial bounding dimensions of the layout (i.e. the screen width and height for a full screen layout) 944 // - errorHandler is used by Clay to inform you if something has gone wrong in configuration or layout. 945 CLAY_DLL_EXPORT Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler); 946 // Returns the Context that clay is currently using. Used when using multiple instances of clay simultaneously. 947 CLAY_DLL_EXPORT Clay_Context* Clay_GetCurrentContext(void); 948 // Sets the context that clay will use to compute the layout. 949 // Used to restore a context saved from Clay_GetCurrentContext when using multiple instances of clay simultaneously. 950 CLAY_DLL_EXPORT void Clay_SetCurrentContext(Clay_Context* context); 951 // Updates the state of Clay's internal scroll data, updating scroll content positions if scrollDelta is non zero, and progressing momentum scrolling. 952 // - enableDragScrolling when set to true will enable mobile device like "touch drag" scroll of scroll containers, including momentum scrolling after the touch has ended. 953 // - scrollDelta is the amount to scroll this frame on each axis in pixels. 954 // - deltaTime is the time in seconds since the last "frame" (scroll update) 955 CLAY_DLL_EXPORT void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime); 956 // Returns the internally stored scroll offset for the currently open element. 957 // Generally intended for use with clip elements to create scrolling containers. 958 CLAY_DLL_EXPORT Clay_Vector2 Clay_GetScrollOffset(void); 959 // Updates the layout dimensions in response to the window or outer container being resized. 960 CLAY_DLL_EXPORT void Clay_SetLayoutDimensions(Clay_Dimensions dimensions); 961 // Called before starting any layout declarations. 962 CLAY_DLL_EXPORT void Clay_BeginLayout(void); 963 // Called when all layout declarations are finished. 964 // Computes the layout and generates and returns the array of render commands to draw. 965 CLAY_DLL_EXPORT Clay_RenderCommandArray Clay_EndLayout(float deltaTime); 966 // Gets the ID of the currently open element, useful for retrieving IDs generated by CLAY_AUTO_ID() 967 CLAY_DLL_EXPORT uint32_t Clay_GetOpenElementId(void); 968 // Calculates a hash ID from the given idString. 969 // Generally only used for dynamic strings when CLAY_ID("stringLiteral") can't be used. 970 CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementId(Clay_String idString); 971 // Calculates a hash ID from the given idString and index. 972 // - index is used to avoid constructing dynamic ID strings in loops. 973 // Generally only used for dynamic strings when CLAY_IDI("stringLiteral", index) can't be used. 974 CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index); 975 // Returns layout data such as the final calculated bounding box for an element with a given ID. 976 // The returned Clay_ElementData contains a `found` bool that will be true if an element with the provided ID was found. 977 // This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings. 978 CLAY_DLL_EXPORT Clay_ElementData Clay_GetElementData(Clay_ElementId id); 979 // Returns true if the pointer position provided by Clay_SetPointerState is within the current element's bounding box. 980 // Works during element declaration, e.g. CLAY({ .backgroundColor = Clay_Hovered() ? BLUE : RED }); 981 CLAY_DLL_EXPORT bool Clay_Hovered(void); 982 // Bind a callback that will be called when the pointer position provided by Clay_SetPointerState is within the current element's bounding box. 983 // - onHoverFunction is a function pointer to a user defined function. 984 // - userData is a pointer that will be transparently passed through when the onHoverFunction is called. 985 CLAY_DLL_EXPORT void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData), void *userData); 986 // An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box. 987 // This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings. 988 CLAY_DLL_EXPORT bool Clay_PointerOver(Clay_ElementId elementId); 989 // Returns the array of element IDs that the pointer is currently over. 990 CLAY_DLL_EXPORT Clay_ElementIdArray Clay_GetPointerOverIds(void); 991 // Returns data representing the state of the scrolling element with the provided ID. 992 // The returned Clay_ScrollContainerData contains a `found` bool that will be true if a scroll element was found with the provided ID. 993 // An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box. 994 // This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings. 995 CLAY_DLL_EXPORT Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id); 996 // Binds a callback function that Clay will call to determine the dimensions of a given string slice. 997 // - measureTextFunction is a user provided function that adheres to the interface Clay_Dimensions (Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); 998 // - userData is a pointer that will be transparently passed through when the measureTextFunction is called. 999 CLAY_DLL_EXPORT void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData); 1000 // Experimental - Used in cases where Clay needs to integrate with a system that manages its own scrolling containers externally. 1001 // Please reach out if you plan to use this function, as it may be subject to change. 1002 CLAY_DLL_EXPORT void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData); 1003 // A bounds-checked "get" function for the Clay_RenderCommandArray returned from Clay_EndLayout(). 1004 CLAY_DLL_EXPORT Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index); 1005 // Enables and disables Clay's internal debug tools. 1006 // This state is retained and does not need to be set each frame. 1007 CLAY_DLL_EXPORT void Clay_SetDebugModeEnabled(bool enabled); 1008 // Returns true if Clay's internal debug tools are currently enabled. 1009 CLAY_DLL_EXPORT bool Clay_IsDebugModeEnabled(void); 1010 // Enables and disables visibility culling. By default, Clay will not generate render commands for elements whose bounding box is entirely outside the screen. 1011 CLAY_DLL_EXPORT void Clay_SetCullingEnabled(bool enabled); 1012 // Returns the maximum number of UI elements supported by Clay's current configuration. 1013 CLAY_DLL_EXPORT int32_t Clay_GetMaxElementCount(void); 1014 // Modifies the maximum number of UI elements supported by Clay's current configuration. 1015 // This may require reallocating additional memory, and re-calling Clay_Initialize(); 1016 CLAY_DLL_EXPORT void Clay_SetMaxElementCount(int32_t maxElementCount); 1017 // Returns the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache. 1018 CLAY_DLL_EXPORT int32_t Clay_GetMaxMeasureTextCacheWordCount(void); 1019 // Modifies the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache. 1020 // This may require reallocating additional memory, and re-calling Clay_Initialize(); 1021 CLAY_DLL_EXPORT void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount); 1022 // Resets Clay's internal text measurement cache. Useful if font mappings have changed or fonts have been reloaded. 1023 CLAY_DLL_EXPORT void Clay_ResetMeasureTextCache(void); 1024 // A built in transition function that uses the "Ease Out" curve 1025 CLAY_DLL_EXPORT bool Clay_EaseOut(Clay_TransitionCallbackArguments arguments); 1026 1027 // Internal API functions required by macros ---------------------- 1028 1029 CLAY_DLL_EXPORT void Clay__OpenElement(void); 1030 CLAY_DLL_EXPORT void Clay__OpenElementWithId(Clay_ElementId elementId); 1031 CLAY_DLL_EXPORT void Clay__ConfigureOpenElement(const Clay_ElementDeclaration config); 1032 CLAY_DLL_EXPORT void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *config); 1033 CLAY_DLL_EXPORT void Clay__CloseElement(void); 1034 CLAY_DLL_EXPORT Clay_ElementId Clay__HashString(Clay_String key, uint32_t seed); 1035 CLAY_DLL_EXPORT Clay_ElementId Clay__HashStringWithOffset(Clay_String key, uint32_t offset, uint32_t seed); 1036 CLAY_DLL_EXPORT void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig textConfig); 1037 1038 extern Clay_Color Clay__debugViewHighlightColor; 1039 extern uint32_t Clay__debugViewWidth; 1040 1041 #ifdef __cplusplus 1042 } 1043 #endif 1044 1045 #endif // CLAY_HEADER 1046 1047 // ----------------------------------------- 1048 // IMPLEMENTATION -------------------------- 1049 // ----------------------------------------- 1050 #ifdef CLAY_IMPLEMENTATION 1051 #undef CLAY_IMPLEMENTATION 1052 1053 #ifndef CLAY__NULL 1054 #define CLAY__NULL 0 1055 #endif 1056 1057 #ifndef CLAY__MAXFLOAT 1058 #define CLAY__MAXFLOAT 3.40282346638528859812e+38F 1059 #endif 1060 1061 Clay_LayoutConfig CLAY_LAYOUT_DEFAULT = CLAY__DEFAULT_STRUCT; 1062 1063 Clay_Color Clay__Color_DEFAULT = CLAY__DEFAULT_STRUCT; 1064 Clay_CornerRadius Clay__CornerRadius_DEFAULT = CLAY__DEFAULT_STRUCT; 1065 Clay_BorderWidth Clay__BorderWidth_DEFAULT = CLAY__DEFAULT_STRUCT; 1066 1067 // The below functions define array bounds checking and convenience functions for a provided type. 1068 #define CLAY__ARRAY_DEFINE_FUNCTIONS(typeName, arrayName) \ 1069 \ 1070 typedef struct \ 1071 { \ 1072 int32_t length; \ 1073 typeName *internalArray; \ 1074 } arrayName##Slice; \ 1075 \ 1076 typeName typeName##_DEFAULT = CLAY__DEFAULT_STRUCT; \ 1077 \ 1078 arrayName arrayName##_Allocate_Arena(int32_t capacity, Clay_Arena *arena) { \ 1079 return CLAY__INIT(arrayName){.capacity = capacity, .length = 0, \ 1080 .internalArray = (typeName *)Clay__Array_Allocate_Arena(capacity, sizeof(typeName), arena)}; \ 1081 } \ 1082 \ 1083 typeName *arrayName##_Get(arrayName *array, int32_t index) { \ 1084 return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &typeName##_DEFAULT; \ 1085 } \ 1086 \ 1087 typeName arrayName##_GetValue(arrayName *array, int32_t index) { \ 1088 return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : typeName##_DEFAULT; \ 1089 } \ 1090 \ 1091 typeName *arrayName##_GetCheckCapacity(arrayName *array, int32_t index) { \ 1092 return Clay__Array_RangeCheck(index, array->capacity) ? &array->internalArray[index] : &typeName##_DEFAULT; \ 1093 } \ 1094 \ 1095 typeName *arrayName##_Add(arrayName *array, typeName item) { \ 1096 if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { \ 1097 array->internalArray[array->length++] = item; \ 1098 return &array->internalArray[array->length - 1]; \ 1099 } \ 1100 return &typeName##_DEFAULT; \ 1101 } \ 1102 \ 1103 typeName *arrayName##Slice_Get(arrayName##Slice *slice, int32_t index) { \ 1104 return Clay__Array_RangeCheck(index, slice->length) ? &slice->internalArray[index] : &typeName##_DEFAULT; \ 1105 } \ 1106 \ 1107 typeName arrayName##_RemoveSwapback(arrayName *array, int32_t index) { \ 1108 if (Clay__Array_RangeCheck(index, array->length)) { \ 1109 array->length--; \ 1110 typeName removed = array->internalArray[index]; \ 1111 array->internalArray[index] = array->internalArray[array->length]; \ 1112 return removed; \ 1113 } \ 1114 return typeName##_DEFAULT; \ 1115 } \ 1116 \ 1117 typeName* arrayName##_Set(arrayName *array, int32_t index, typeName value) { \ 1118 if (Clay__Array_RangeCheck(index, array->capacity)) { \ 1119 array->internalArray[index] = value; \ 1120 array->length = index < array->length ? array->length : index + 1; \ 1121 return &array->internalArray[index];\ 1122 } \ 1123 return NULL;\ 1124 } \ 1125 \ 1126 typeName* arrayName##_Set_DontTouchLength(arrayName *array, int32_t index, typeName value) { \ 1127 if (Clay__Array_RangeCheck(index, array->capacity)) { \ 1128 array->internalArray[index] = value; \ 1129 return &array->internalArray[index];\ 1130 } \ 1131 return NULL;\ 1132 } \ 1133 1134 #define CLAY__ARRAY_DEFINE(typeName, arrayName) \ 1135 typedef struct \ 1136 { \ 1137 int32_t capacity; \ 1138 int32_t length; \ 1139 typeName *internalArray; \ 1140 } arrayName; \ 1141 \ 1142 CLAY__ARRAY_DEFINE_FUNCTIONS(typeName, arrayName) \ 1143 1144 Clay_Context *Clay__currentContext; 1145 int32_t Clay__defaultMaxElementCount = 8192; 1146 int32_t Clay__defaultMaxMeasureTextWordCacheCount = 16384; 1147 1148 void Clay__ErrorHandlerFunctionDefault(Clay_ErrorData errorText) { 1149 (void) errorText; 1150 } 1151 1152 Clay_String CLAY__SPACECHAR = { .length = 1, .chars = " " }; 1153 Clay_String CLAY__STRING_DEFAULT = { .length = 0, .chars = NULL }; 1154 1155 typedef struct { 1156 bool maxElementsExceeded; 1157 bool maxRenderCommandsExceeded; 1158 bool maxTextMeasureCacheExceeded; 1159 bool textMeasurementFunctionNotSet; 1160 } Clay_BooleanWarnings; 1161 1162 typedef struct { 1163 Clay_String baseMessage; 1164 Clay_String dynamicMessage; 1165 } Clay__Warning; 1166 1167 Clay__Warning CLAY__WARNING_DEFAULT = CLAY__DEFAULT_STRUCT; 1168 1169 typedef struct { 1170 int32_t capacity; 1171 int32_t length; 1172 Clay__Warning *internalArray; 1173 } Clay__WarningArray; 1174 1175 Clay__WarningArray Clay__WarningArray_Allocate_Arena(int32_t capacity, Clay_Arena *arena); 1176 Clay__Warning *Clay__WarningArray_Add(Clay__WarningArray *array, Clay__Warning item); 1177 void* Clay__Array_Allocate_Arena(int32_t capacity, uint32_t itemSize, Clay_Arena *arena); 1178 bool Clay__Array_RangeCheck(int32_t index, int32_t length); 1179 bool Clay__Array_AddCapacityCheck(int32_t length, int32_t capacity); 1180 1181 CLAY__ARRAY_DEFINE(bool, Clay__boolArray) 1182 CLAY__ARRAY_DEFINE(int32_t, Clay__int32_tArray) 1183 CLAY__ARRAY_DEFINE(char, Clay__charArray) 1184 CLAY__ARRAY_DEFINE_FUNCTIONS(Clay_ElementId, Clay_ElementIdArray) 1185 CLAY__ARRAY_DEFINE(Clay_String, Clay__StringArray) 1186 CLAY__ARRAY_DEFINE_FUNCTIONS(Clay_RenderCommand, Clay_RenderCommandArray) 1187 1188 typedef struct { 1189 Clay_Dimensions dimensions; 1190 Clay_String line; 1191 } Clay__WrappedTextLine; 1192 1193 CLAY__ARRAY_DEFINE(Clay__WrappedTextLine, Clay__WrappedTextLineArray) 1194 1195 typedef struct { 1196 Clay_String text; 1197 Clay_Dimensions preferredDimensions; 1198 Clay__WrappedTextLineArraySlice wrappedLines; 1199 } Clay__TextElementData; 1200 1201 typedef struct { 1202 int32_t *elements; 1203 uint16_t length; 1204 } Clay__LayoutElementChildren; 1205 1206 typedef struct Clay_LayoutElement { 1207 Clay__LayoutElementChildren children; 1208 Clay_Dimensions dimensions; 1209 Clay_Dimensions minDimensions; 1210 union { 1211 Clay_ElementDeclaration config; 1212 struct { 1213 Clay_TextElementConfig textConfig; 1214 Clay__TextElementData textElementData; 1215 }; 1216 }; 1217 uint32_t id; 1218 uint16_t floatingChildrenCount; 1219 bool isTextElement; 1220 // True if the element is currently in an exit transition, and is "synthetic" 1221 // i.e. data was retained from previous frames 1222 bool exiting; 1223 } Clay_LayoutElement; 1224 1225 CLAY__ARRAY_DEFINE(Clay_LayoutElement, Clay_LayoutElementArray) 1226 1227 typedef struct { 1228 Clay_LayoutElement *layoutElement; 1229 Clay_BoundingBox boundingBox; 1230 Clay_Dimensions contentSize; 1231 Clay_Vector2 scrollOrigin; 1232 Clay_Vector2 pointerOrigin; 1233 Clay_Vector2 scrollMomentum; 1234 Clay_Vector2 scrollPosition; 1235 Clay_Vector2 previousDelta; 1236 float momentumTime; 1237 uint32_t elementId; 1238 bool openThisFrame; 1239 bool pointerScrollActive; 1240 } Clay__ScrollContainerDataInternal; 1241 1242 CLAY__ARRAY_DEFINE(Clay__ScrollContainerDataInternal, Clay__ScrollContainerDataInternalArray) 1243 1244 // Data representing the current internal state of a transition element. 1245 typedef struct Clay__TransitionDataInternal { 1246 Clay_TransitionData initialState; 1247 Clay_TransitionData currentState; 1248 Clay_TransitionData targetState; 1249 Clay_LayoutElement* elementThisFrame; 1250 Clay_Vector2 oldParentRelativePosition; 1251 uint32_t elementId; 1252 uint32_t parentId; 1253 uint32_t siblingIndex; 1254 float elapsedTime; 1255 Clay_TransitionState state; 1256 bool transitionOut; 1257 bool reparented; 1258 Clay_TransitionProperty activeProperties; 1259 } Clay__TransitionDataInternal; 1260 1261 CLAY__ARRAY_DEFINE(Clay__TransitionDataInternal, Clay__TransitionDataInternalArray) 1262 1263 typedef struct { 1264 bool collision; 1265 bool collapsed; 1266 } Clay__DebugElementData; 1267 1268 CLAY__ARRAY_DEFINE(Clay__DebugElementData, Clay__DebugElementDataArray) 1269 1270 typedef struct { // todo get this struct into a single cache line 1271 Clay_BoundingBox boundingBox; 1272 Clay_ElementId elementId; 1273 Clay_LayoutElement* layoutElement; 1274 void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData); 1275 void *hoverFunctionUserData; 1276 int32_t nextIndex; 1277 uint32_t generation; 1278 bool appearedThisFrame; 1279 Clay__DebugElementData *debugData; 1280 } Clay_LayoutElementHashMapItem; 1281 1282 CLAY__ARRAY_DEFINE(Clay_LayoutElementHashMapItem, Clay__LayoutElementHashMapItemArray) 1283 1284 typedef struct { 1285 int32_t startOffset; 1286 int32_t length; 1287 float width; 1288 int32_t next; 1289 } Clay__MeasuredWord; 1290 1291 CLAY__ARRAY_DEFINE(Clay__MeasuredWord, Clay__MeasuredWordArray) 1292 1293 typedef struct { 1294 Clay_Dimensions unwrappedDimensions; 1295 int32_t measuredWordsStartIndex; 1296 float minWidth; 1297 bool containsNewlines; 1298 // Hash map data 1299 uint32_t id; 1300 int32_t nextIndex; 1301 uint32_t generation; 1302 } Clay__MeasureTextCacheItem; 1303 1304 CLAY__ARRAY_DEFINE(Clay__MeasureTextCacheItem, Clay__MeasureTextCacheItemArray) 1305 1306 typedef struct { 1307 Clay_LayoutElement *layoutElement; 1308 Clay_Vector2 position; 1309 Clay_Vector2 nextChildOffset; 1310 bool parentMovedThisFramed; // Used to relativise transitions 1311 } Clay__LayoutElementTreeNode; 1312 1313 CLAY__ARRAY_DEFINE(Clay__LayoutElementTreeNode, Clay__LayoutElementTreeNodeArray) 1314 1315 typedef struct { 1316 int32_t layoutElementIndex; 1317 uint32_t parentId; // This can be zero in the case of the root layout tree 1318 uint32_t clipElementId; // This can be zero if there is no clip element 1319 int16_t zIndex; 1320 Clay_Vector2 pointerOffset; // Only used when scroll containers are managed externally 1321 } Clay__LayoutElementTreeRoot; 1322 1323 CLAY__ARRAY_DEFINE(Clay__LayoutElementTreeRoot, Clay__LayoutElementTreeRootArray) 1324 1325 struct Clay_Context { 1326 int32_t maxElementCount; 1327 int32_t maxMeasureTextCacheWordCount; 1328 int32_t exitingElementsLength; 1329 int32_t exitingElementsChildrenLength; 1330 bool warningsEnabled; 1331 bool rootResizedLastFrame; 1332 Clay_ErrorHandler errorHandler; 1333 Clay_BooleanWarnings booleanWarnings; 1334 Clay__WarningArray warnings; 1335 1336 Clay_PointerData pointerInfo; 1337 Clay_Dimensions layoutDimensions; 1338 Clay_ElementId dynamicElementIndexBaseHash; 1339 uint32_t dynamicElementIndex; 1340 bool debugModeEnabled; 1341 bool disableCulling; 1342 bool externalScrollHandlingEnabled; 1343 uint32_t debugSelectedElementId; 1344 uint32_t generation; 1345 uintptr_t arenaResetOffset; 1346 void *measureTextUserData; 1347 void *queryScrollOffsetUserData; 1348 Clay_Arena internalArena; 1349 // Layout Elements / Render Commands 1350 Clay_LayoutElementArray layoutElements; 1351 Clay_RenderCommandArray renderCommands; 1352 Clay__int32_tArray openLayoutElementStack; 1353 Clay__int32_tArray layoutElementChildren; 1354 Clay__int32_tArray layoutElementChildrenBuffer; 1355 Clay__int32_tArray reusableElementIndexBuffer; 1356 Clay__int32_tArray layoutElementClipElementIds; 1357 // Misc Data Structures 1358 Clay__StringArray layoutElementIdStrings; 1359 Clay__WrappedTextLineArray wrappedTextLines; 1360 Clay__LayoutElementTreeNodeArray layoutElementTreeNodeArray1; 1361 Clay__LayoutElementTreeRootArray layoutElementTreeRoots; 1362 Clay__LayoutElementHashMapItemArray layoutElementsHashMapInternal; 1363 Clay__int32_tArray layoutElementsHashMap; 1364 Clay__MeasureTextCacheItemArray measureTextHashMapInternal; 1365 Clay__int32_tArray measureTextHashMapInternalFreeList; 1366 Clay__int32_tArray measureTextHashMap; 1367 Clay__MeasuredWordArray measuredWords; 1368 Clay__int32_tArray measuredWordsFreeList; 1369 Clay__int32_tArray openClipElementStack; 1370 Clay_ElementIdArray pointerOverIds; 1371 Clay__ScrollContainerDataInternalArray scrollContainerDatas; 1372 Clay__TransitionDataInternalArray transitionDatas; 1373 Clay__boolArray treeNodeVisited; 1374 Clay__charArray dynamicStringData; 1375 Clay__DebugElementDataArray debugElementData; 1376 }; 1377 1378 Clay_Context* Clay__Context_Allocate_Arena(Clay_Arena *arena) { 1379 size_t totalSizeBytes = sizeof(Clay_Context); 1380 if (totalSizeBytes > arena->capacity) 1381 { 1382 return NULL; 1383 } 1384 arena->nextAllocation += totalSizeBytes; 1385 return (Clay_Context*)(arena->memory); 1386 } 1387 1388 Clay_String Clay__WriteStringToCharBuffer(Clay__charArray *buffer, Clay_String string) { 1389 for (int32_t i = 0; i < string.length; i++) { 1390 buffer->internalArray[buffer->length + i] = string.chars[i]; 1391 } 1392 buffer->length += string.length; 1393 return CLAY__INIT(Clay_String) { .length = string.length, .chars = (const char *)(buffer->internalArray + buffer->length - string.length) }; 1394 } 1395 1396 #ifdef CLAY_WASM 1397 __attribute__((import_module("clay"), import_name("measureTextFunction"))) Clay_Dimensions Clay__MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); 1398 __attribute__((import_module("clay"), import_name("queryScrollOffsetFunction"))) Clay_Vector2 Clay__QueryScrollOffset(uint32_t elementId, void *userData); 1399 #else 1400 Clay_Dimensions (*Clay__MeasureText)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); 1401 Clay_Vector2 (*Clay__QueryScrollOffset)(uint32_t elementId, void *userData); 1402 #endif 1403 1404 Clay_LayoutElement* Clay__GetOpenLayoutElement(void) { 1405 Clay_Context* context = Clay_GetCurrentContext(); 1406 return Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1)); 1407 } 1408 1409 Clay_LayoutElement* Clay__GetParentElement(void) { 1410 Clay_Context* context = Clay_GetCurrentContext(); 1411 return Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); 1412 } 1413 1414 uint32_t Clay__GetParentElementId(void) { 1415 return Clay__GetParentElement()->id; 1416 } 1417 1418 bool Clay__BorderHasAnyWidth(Clay_BorderElementConfig* borderConfig) { 1419 return borderConfig->width.betweenChildren > 0 || borderConfig->width.left > 0 || borderConfig->width.right > 0 || borderConfig->width.top > 0 || borderConfig->width.bottom > 0; 1420 } 1421 1422 Clay_ElementId Clay__HashNumber(const uint32_t offset, const uint32_t seed) { 1423 uint32_t hash = seed; 1424 hash += (offset + 48); 1425 hash += (hash << 10); 1426 hash ^= (hash >> 6); 1427 1428 hash += (hash << 3); 1429 hash ^= (hash >> 11); 1430 hash += (hash << 15); 1431 return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = seed, .stringId = CLAY__STRING_DEFAULT }; // Reserve the hash result of zero as "null id" 1432 } 1433 1434 Clay_ElementId Clay__HashString(Clay_String key, const uint32_t seed) { 1435 uint32_t hash = seed; 1436 1437 for (int32_t i = 0; i < key.length; i++) { 1438 hash += key.chars[i]; 1439 hash += (hash << 10); 1440 hash ^= (hash >> 6); 1441 } 1442 1443 hash += (hash << 3); 1444 hash ^= (hash >> 11); 1445 hash += (hash << 15); 1446 return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = 0, .baseId = hash + 1, .stringId = key }; // Reserve the hash result of zero as "null id" 1447 } 1448 1449 Clay_ElementId Clay__HashStringWithOffset(Clay_String key, const uint32_t offset, const uint32_t seed) { 1450 uint32_t hash = 0; 1451 uint32_t base = seed; 1452 1453 for (int32_t i = 0; i < key.length; i++) { 1454 base += key.chars[i]; 1455 base += (base << 10); 1456 base ^= (base >> 6); 1457 } 1458 hash = base; 1459 hash += offset; 1460 hash += (hash << 10); 1461 hash ^= (hash >> 6); 1462 1463 hash += (hash << 3); 1464 base += (base << 3); 1465 hash ^= (hash >> 11); 1466 base ^= (base >> 11); 1467 hash += (hash << 15); 1468 base += (base << 15); 1469 return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id" 1470 } 1471 1472 #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) 1473 static inline __m128i Clay__SIMDRotateLeft(__m128i x, int r) { 1474 return _mm_or_si128(_mm_slli_epi64(x, r), _mm_srli_epi64(x, 64 - r)); 1475 } 1476 1477 static inline void Clay__SIMDARXMix(__m128i* a, __m128i* b) { 1478 *a = _mm_add_epi64(*a, *b); 1479 *b = _mm_xor_si128(Clay__SIMDRotateLeft(*b, 17), *a); 1480 } 1481 1482 uint64_t Clay__HashData(const uint8_t* data, size_t length) { 1483 // Pinched these constants from the BLAKE implementation 1484 __m128i v0 = _mm_set1_epi64x(0x6a09e667f3bcc908ULL); 1485 __m128i v1 = _mm_set1_epi64x(0xbb67ae8584caa73bULL); 1486 __m128i v2 = _mm_set1_epi64x(0x3c6ef372fe94f82bULL); 1487 __m128i v3 = _mm_set1_epi64x(0xa54ff53a5f1d36f1ULL); 1488 1489 uint8_t overflowBuffer[16] = { 0 }; // Temporary buffer for small inputs 1490 1491 while (length > 0) { 1492 __m128i msg; 1493 if (length >= 16) { 1494 msg = _mm_loadu_si128((const __m128i*)data); 1495 data += 16; 1496 length -= 16; 1497 } 1498 else { 1499 for (size_t i = 0; i < length; i++) { 1500 overflowBuffer[i] = data[i]; 1501 } 1502 msg = _mm_loadu_si128((const __m128i*)overflowBuffer); 1503 length = 0; 1504 } 1505 1506 v0 = _mm_xor_si128(v0, msg); 1507 Clay__SIMDARXMix(&v0, &v1); 1508 Clay__SIMDARXMix(&v2, &v3); 1509 1510 v0 = _mm_add_epi64(v0, v2); 1511 v1 = _mm_add_epi64(v1, v3); 1512 } 1513 1514 Clay__SIMDARXMix(&v0, &v1); 1515 Clay__SIMDARXMix(&v2, &v3); 1516 v0 = _mm_add_epi64(v0, v2); 1517 v1 = _mm_add_epi64(v1, v3); 1518 v0 = _mm_add_epi64(v0, v1); 1519 1520 uint64_t result[2]; 1521 _mm_storeu_si128((__m128i*)result, v0); 1522 1523 return result[0] ^ result[1]; 1524 } 1525 #elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) 1526 static inline void Clay__SIMDARXMix(uint64x2_t* a, uint64x2_t* b) { 1527 *a = vaddq_u64(*a, *b); 1528 *b = veorq_u64(vorrq_u64(vshlq_n_u64(*b, 17), vshrq_n_u64(*b, 64 - 17)), *a); 1529 } 1530 1531 uint64_t Clay__HashData(const uint8_t* data, size_t length) { 1532 // Pinched these constants from the BLAKE implementation 1533 uint64x2_t v0 = vdupq_n_u64(0x6a09e667f3bcc908ULL); 1534 uint64x2_t v1 = vdupq_n_u64(0xbb67ae8584caa73bULL); 1535 uint64x2_t v2 = vdupq_n_u64(0x3c6ef372fe94f82bULL); 1536 uint64x2_t v3 = vdupq_n_u64(0xa54ff53a5f1d36f1ULL); 1537 1538 uint8_t overflowBuffer[8] = { 0 }; 1539 1540 while (length > 0) { 1541 uint64x2_t msg; 1542 if (length > 16) { 1543 msg = vld1q_u64((const uint64_t*)data); 1544 data += 16; 1545 length -= 16; 1546 } 1547 else if (length > 8) { 1548 msg = vcombine_u64(vld1_u64((const uint64_t*)data), vdup_n_u64(0)); 1549 data += 8; 1550 length -= 8; 1551 } 1552 else { 1553 for (size_t i = 0; i < length; i++) { 1554 overflowBuffer[i] = data[i]; 1555 } 1556 uint8x8_t lower = vld1_u8(overflowBuffer); 1557 msg = vreinterpretq_u64_u8(vcombine_u8(lower, vdup_n_u8(0))); 1558 length = 0; 1559 } 1560 v0 = veorq_u64(v0, msg); 1561 Clay__SIMDARXMix(&v0, &v1); 1562 Clay__SIMDARXMix(&v2, &v3); 1563 1564 v0 = vaddq_u64(v0, v2); 1565 v1 = vaddq_u64(v1, v3); 1566 } 1567 1568 Clay__SIMDARXMix(&v0, &v1); 1569 Clay__SIMDARXMix(&v2, &v3); 1570 v0 = vaddq_u64(v0, v2); 1571 v1 = vaddq_u64(v1, v3); 1572 v0 = vaddq_u64(v0, v1); 1573 1574 uint64_t result[2]; 1575 vst1q_u64(result, v0); 1576 1577 return result[0] ^ result[1]; 1578 } 1579 #else 1580 uint64_t Clay__HashData(const uint8_t* data, size_t length) { 1581 uint64_t hash = 0; 1582 1583 for (size_t i = 0; i < length; i++) { 1584 hash += data[i]; 1585 hash += (hash << 10); 1586 hash ^= (hash >> 6); 1587 } 1588 return hash; 1589 } 1590 #endif 1591 1592 uint32_t Clay__HashStringContentsWithConfig(Clay_String *text, Clay_TextElementConfig *config) { 1593 uint32_t hash = 0; 1594 if (text->isStaticallyAllocated) { 1595 hash += (uintptr_t)text->chars; 1596 hash += (hash << 10); 1597 hash ^= (hash >> 6); 1598 hash += text->length; 1599 hash += (hash << 10); 1600 hash ^= (hash >> 6); 1601 } else { 1602 hash = Clay__HashData((const uint8_t *)text->chars, text->length) % UINT32_MAX; 1603 } 1604 1605 hash += config->fontId; 1606 hash += (hash << 10); 1607 hash ^= (hash >> 6); 1608 1609 hash += config->fontSize; 1610 hash += (hash << 10); 1611 hash ^= (hash >> 6); 1612 1613 hash += config->letterSpacing; 1614 hash += (hash << 10); 1615 hash ^= (hash >> 6); 1616 1617 hash += (hash << 3); 1618 hash ^= (hash >> 11); 1619 hash += (hash << 15); 1620 return hash + 1; // Reserve the hash result of zero as "null id" 1621 } 1622 1623 Clay__MeasuredWord *Clay__AddMeasuredWord(Clay__MeasuredWord word, Clay__MeasuredWord *previousWord) { 1624 Clay_Context* context = Clay_GetCurrentContext(); 1625 if (context->measuredWordsFreeList.length > 0) { 1626 uint32_t newItemIndex = Clay__int32_tArray_GetValue(&context->measuredWordsFreeList, (int)context->measuredWordsFreeList.length - 1); 1627 context->measuredWordsFreeList.length--; 1628 Clay__MeasuredWordArray_Set(&context->measuredWords, (int)newItemIndex, word); 1629 previousWord->next = (int32_t)newItemIndex; 1630 return Clay__MeasuredWordArray_Get(&context->measuredWords, (int)newItemIndex); 1631 } else { 1632 previousWord->next = (int32_t)context->measuredWords.length; 1633 return Clay__MeasuredWordArray_Add(&context->measuredWords, word); 1634 } 1635 } 1636 1637 Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_TextElementConfig *config) { 1638 Clay_Context* context = Clay_GetCurrentContext(); 1639 #ifndef CLAY_WASM 1640 if (!Clay__MeasureText) { 1641 if (!context->booleanWarnings.textMeasurementFunctionNotSet) { 1642 context->booleanWarnings.textMeasurementFunctionNotSet = true; 1643 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 1644 .errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, 1645 .errorText = CLAY_STRING("Clay's internal MeasureText function is null. You may have forgotten to call Clay_SetMeasureTextFunction(), or passed a NULL function pointer by mistake."), 1646 .userData = context->errorHandler.userData }); 1647 } 1648 return &Clay__MeasureTextCacheItem_DEFAULT; 1649 } 1650 #endif 1651 uint32_t id = Clay__HashStringContentsWithConfig(text, config); 1652 uint32_t hashBucket = id % (context->maxMeasureTextCacheWordCount / 32); 1653 int32_t elementIndexPrevious = 0; 1654 int32_t elementIndex = context->measureTextHashMap.internalArray[hashBucket]; 1655 while (elementIndex != 0) { 1656 Clay__MeasureTextCacheItem *hashEntry = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, elementIndex); 1657 if (hashEntry->id == id) { 1658 hashEntry->generation = context->generation; 1659 return hashEntry; 1660 } 1661 // This element hasn't been seen in a few frames, delete the hash map item 1662 if (context->generation - hashEntry->generation > 2) { 1663 // Add all the measured words that were included in this measurement to the freelist 1664 int32_t nextWordIndex = hashEntry->measuredWordsStartIndex; 1665 while (nextWordIndex != -1) { 1666 Clay__MeasuredWord *measuredWord = Clay__MeasuredWordArray_Get(&context->measuredWords, nextWordIndex); 1667 Clay__int32_tArray_Add(&context->measuredWordsFreeList, nextWordIndex); 1668 nextWordIndex = measuredWord->next; 1669 } 1670 1671 int32_t nextIndex = hashEntry->nextIndex; 1672 Clay__MeasureTextCacheItemArray_Set(&context->measureTextHashMapInternal, elementIndex, CLAY__INIT(Clay__MeasureTextCacheItem) { .measuredWordsStartIndex = -1 }); 1673 Clay__int32_tArray_Add(&context->measureTextHashMapInternalFreeList, elementIndex); 1674 if (elementIndexPrevious == 0) { 1675 context->measureTextHashMap.internalArray[hashBucket] = nextIndex; 1676 } else { 1677 Clay__MeasureTextCacheItem *previousHashEntry = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, elementIndexPrevious); 1678 previousHashEntry->nextIndex = nextIndex; 1679 } 1680 elementIndex = nextIndex; 1681 } else { 1682 elementIndexPrevious = elementIndex; 1683 elementIndex = hashEntry->nextIndex; 1684 } 1685 } 1686 1687 int32_t newItemIndex = 0; 1688 Clay__MeasureTextCacheItem newCacheItem = { .measuredWordsStartIndex = -1, .id = id, .generation = context->generation }; 1689 Clay__MeasureTextCacheItem *measured = NULL; 1690 if (context->measureTextHashMapInternalFreeList.length > 0) { 1691 newItemIndex = Clay__int32_tArray_GetValue(&context->measureTextHashMapInternalFreeList, context->measureTextHashMapInternalFreeList.length - 1); 1692 context->measureTextHashMapInternalFreeList.length--; 1693 Clay__MeasureTextCacheItemArray_Set(&context->measureTextHashMapInternal, newItemIndex, newCacheItem); 1694 measured = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, newItemIndex); 1695 } else { 1696 if (context->measureTextHashMapInternal.length == context->measureTextHashMapInternal.capacity - 1) { 1697 if (!context->booleanWarnings.maxTextMeasureCacheExceeded) { 1698 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 1699 .errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, 1700 .errorText = CLAY_STRING("Clay ran out of capacity while attempting to measure text elements. Try using Clay_SetMaxElementCount() with a higher value."), 1701 .userData = context->errorHandler.userData }); 1702 context->booleanWarnings.maxTextMeasureCacheExceeded = true; 1703 } 1704 return &Clay__MeasureTextCacheItem_DEFAULT; 1705 } 1706 measured = Clay__MeasureTextCacheItemArray_Add(&context->measureTextHashMapInternal, newCacheItem); 1707 newItemIndex = context->measureTextHashMapInternal.length - 1; 1708 } 1709 1710 int32_t start = 0; 1711 int32_t end = 0; 1712 float lineWidth = 0; 1713 float measuredWidth = 0; 1714 float measuredHeight = 0; 1715 float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, config, context->measureTextUserData).width; 1716 Clay__MeasuredWord tempWord = { .next = -1 }; 1717 Clay__MeasuredWord *previousWord = &tempWord; 1718 while (end < text->length) { 1719 if (context->measuredWords.length == context->measuredWords.capacity - 1) { 1720 if (!context->booleanWarnings.maxTextMeasureCacheExceeded) { 1721 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 1722 .errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, 1723 .errorText = CLAY_STRING("Clay has run out of space in it's internal text measurement cache. Try using Clay_SetMaxMeasureTextCacheWordCount() (default 16384, with 1 unit storing 1 measured word)."), 1724 .userData = context->errorHandler.userData }); 1725 context->booleanWarnings.maxTextMeasureCacheExceeded = true; 1726 } 1727 return &Clay__MeasureTextCacheItem_DEFAULT; 1728 } 1729 char current = text->chars[end]; 1730 if (current == ' ' || current == '\n') { 1731 int32_t length = end - start; 1732 Clay_Dimensions dimensions = CLAY__DEFAULT_STRUCT; 1733 if (length > 0) { 1734 dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) {.length = length, .chars = &text->chars[start], .baseChars = text->chars}, config, context->measureTextUserData); 1735 } 1736 measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth); 1737 measuredHeight = CLAY__MAX(measuredHeight, dimensions.height); 1738 if (current == ' ') { 1739 dimensions.width += spaceWidth; 1740 previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length + 1, .width = dimensions.width, .next = -1 }, previousWord); 1741 lineWidth += dimensions.width; 1742 } 1743 if (current == '\n') { 1744 if (length > 0) { 1745 previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length, .width = dimensions.width, .next = -1 }, previousWord); 1746 } 1747 previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = end + 1, .length = 0, .width = 0, .next = -1 }, previousWord); 1748 lineWidth += dimensions.width; 1749 measuredWidth = CLAY__MAX(lineWidth, measuredWidth); 1750 measured->containsNewlines = true; 1751 lineWidth = 0; 1752 } 1753 start = end + 1; 1754 } 1755 end++; 1756 } 1757 if (end - start > 0) { 1758 Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = end - start, .chars = &text->chars[start], .baseChars = text->chars }, config, context->measureTextUserData); 1759 Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = end - start, .width = dimensions.width, .next = -1 }, previousWord); 1760 lineWidth += dimensions.width; 1761 measuredHeight = CLAY__MAX(measuredHeight, dimensions.height); 1762 measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth); 1763 } 1764 measuredWidth = CLAY__MAX(lineWidth, measuredWidth) - config->letterSpacing; 1765 1766 measured->measuredWordsStartIndex = tempWord.next; 1767 measured->unwrappedDimensions.width = measuredWidth; 1768 measured->unwrappedDimensions.height = measuredHeight; 1769 1770 if (elementIndexPrevious != 0) { 1771 Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, elementIndexPrevious)->nextIndex = newItemIndex; 1772 } else { 1773 context->measureTextHashMap.internalArray[hashBucket] = newItemIndex; 1774 } 1775 return measured; 1776 } 1777 1778 bool Clay__PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { 1779 return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height; 1780 } 1781 1782 Clay_LayoutElementHashMapItem* Clay__AddHashMapItem(Clay_ElementId elementId, Clay_LayoutElement* layoutElement) { 1783 Clay_Context* context = Clay_GetCurrentContext(); 1784 if (context->layoutElementsHashMapInternal.length == context->layoutElementsHashMapInternal.capacity - 1) { 1785 return NULL; 1786 } 1787 Clay_LayoutElementHashMapItem item = { .elementId = elementId, .layoutElement = layoutElement, .nextIndex = -1, .generation = context->generation + 1, .appearedThisFrame = true }; 1788 uint32_t hashBucket = elementId.id % context->layoutElementsHashMap.capacity; 1789 int32_t hashItemPrevious = -1; 1790 int32_t hashItemIndex = context->layoutElementsHashMap.internalArray[hashBucket]; 1791 while (hashItemIndex != -1) { // Just replace collision, not a big deal - leave it up to the end user 1792 Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Get(&context->layoutElementsHashMapInternal, hashItemIndex); 1793 if (hashItem->elementId.id == elementId.id) { // Collision - resolve based on generation 1794 item.nextIndex = hashItem->nextIndex; 1795 if (hashItem->generation <= context->generation) { // First collision - assume this is the "same" element 1796 hashItem->appearedThisFrame = hashItem->generation < context->generation; 1797 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. 1798 hashItem->generation = context->generation + 1; 1799 hashItem->layoutElement = layoutElement; 1800 hashItem->debugData->collision = false; 1801 hashItem->onHoverFunction = NULL; 1802 hashItem->hoverFunctionUserData = 0; 1803 } else { // Multiple collisions this frame - two elements have the same ID 1804 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 1805 .errorType = CLAY_ERROR_TYPE_DUPLICATE_ID, 1806 .errorText = CLAY_STRING("An element with this ID was already previously declared during this layout."), 1807 .userData = context->errorHandler.userData }); 1808 if (context->debugModeEnabled) { 1809 hashItem->debugData->collision = true; 1810 } 1811 } 1812 return hashItem; 1813 } 1814 hashItemPrevious = hashItemIndex; 1815 hashItemIndex = hashItem->nextIndex; 1816 } 1817 Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Add(&context->layoutElementsHashMapInternal, item); 1818 hashItem->debugData = Clay__DebugElementDataArray_Add(&context->debugElementData, CLAY__INIT(Clay__DebugElementData) CLAY__DEFAULT_STRUCT); 1819 if (hashItemPrevious != -1) { 1820 Clay__LayoutElementHashMapItemArray_Get(&context->layoutElementsHashMapInternal, hashItemPrevious)->nextIndex = (int32_t)context->layoutElementsHashMapInternal.length - 1; 1821 } else { 1822 context->layoutElementsHashMap.internalArray[hashBucket] = (int32_t)context->layoutElementsHashMapInternal.length - 1; 1823 } 1824 return hashItem; 1825 } 1826 1827 Clay_LayoutElementHashMapItem *Clay__GetHashMapItem(uint32_t id) { 1828 Clay_Context* context = Clay_GetCurrentContext(); 1829 uint32_t hashBucket = id % context->layoutElementsHashMap.capacity; 1830 int32_t elementIndex = context->layoutElementsHashMap.internalArray[hashBucket]; 1831 while (elementIndex != -1) { 1832 Clay_LayoutElementHashMapItem *hashEntry = Clay__LayoutElementHashMapItemArray_Get(&context->layoutElementsHashMapInternal, elementIndex); 1833 if (hashEntry->elementId.id == id) { 1834 return hashEntry; 1835 } 1836 elementIndex = hashEntry->nextIndex; 1837 } 1838 return &Clay_LayoutElementHashMapItem_DEFAULT; 1839 } 1840 1841 void Clay__UpdateAspectRatioBox(Clay_LayoutElement *layoutElement) { 1842 if (layoutElement->config.aspectRatio.aspectRatio != 0) { 1843 if (layoutElement->dimensions.width == 0 && layoutElement->dimensions.height != 0) { 1844 layoutElement->dimensions.width = layoutElement->dimensions.height * layoutElement->config.aspectRatio.aspectRatio; 1845 } else if (layoutElement->dimensions.width != 0 && layoutElement->dimensions.height == 0) { 1846 layoutElement->dimensions.height = layoutElement->dimensions.width * (1 / layoutElement->config.aspectRatio.aspectRatio); 1847 } 1848 } 1849 } 1850 1851 void Clay__CloseElement(void) { 1852 Clay_Context* context = Clay_GetCurrentContext(); 1853 if (context->booleanWarnings.maxElementsExceeded) { 1854 return; 1855 } 1856 Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); 1857 Clay_LayoutConfig *layoutConfig = &openLayoutElement->config.layout; 1858 bool elementHasClipHorizontal = openLayoutElement->config.clip.horizontal; 1859 bool elementHasClipVertical = openLayoutElement->config.clip.vertical; 1860 if (elementHasClipHorizontal || elementHasClipVertical || openLayoutElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { 1861 context->openClipElementStack.length--; 1862 } 1863 1864 float leftRightPadding = (float)(layoutConfig->padding.left + layoutConfig->padding.right); 1865 float topBottomPadding = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom); 1866 1867 // Attach children to the current open element 1868 openLayoutElement->children.elements = &context->layoutElementChildren.internalArray[context->layoutElementChildren.length]; 1869 if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { 1870 openLayoutElement->dimensions.width = leftRightPadding; 1871 openLayoutElement->minDimensions.width = leftRightPadding; 1872 for (int32_t i = 0; i < openLayoutElement->children.length; i++) { 1873 int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->children.length + i); 1874 Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex); 1875 openLayoutElement->dimensions.width += child->dimensions.width; 1876 openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + topBottomPadding); 1877 // Minimum size of child elements doesn't matter to clip containers as they can shrink and hide their contents 1878 if (!elementHasClipHorizontal) { 1879 openLayoutElement->minDimensions.width += child->minDimensions.width; 1880 } 1881 if (!elementHasClipVertical) { 1882 openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + topBottomPadding); 1883 } 1884 Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); 1885 } 1886 float childGap = (float)(CLAY__MAX(openLayoutElement->children.length - 1, 0) * layoutConfig->childGap); 1887 openLayoutElement->dimensions.width += childGap; 1888 if (!elementHasClipHorizontal) { 1889 openLayoutElement->minDimensions.width += childGap; 1890 } 1891 } 1892 else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { 1893 openLayoutElement->dimensions.height = topBottomPadding; 1894 openLayoutElement->minDimensions.height = topBottomPadding; 1895 for (int32_t i = 0; i < openLayoutElement->children.length; i++) { 1896 int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->children.length + i); 1897 Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex); 1898 openLayoutElement->dimensions.height += child->dimensions.height; 1899 openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + leftRightPadding); 1900 // Minimum size of child elements doesn't matter to clip containers as they can shrink and hide their contents 1901 if (!elementHasClipVertical) { 1902 openLayoutElement->minDimensions.height += child->minDimensions.height; 1903 } 1904 if (!elementHasClipHorizontal) { 1905 openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + leftRightPadding); 1906 } 1907 Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); 1908 } 1909 float childGap = (float)(CLAY__MAX(openLayoutElement->children.length - 1, 0) * layoutConfig->childGap); 1910 openLayoutElement->dimensions.height += childGap; 1911 if (!elementHasClipVertical) { 1912 openLayoutElement->minDimensions.height += childGap; 1913 } 1914 } 1915 1916 context->layoutElementChildrenBuffer.length -= openLayoutElement->children.length; 1917 1918 // Clamp element min and max width to the values configured in the layout 1919 if (layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { 1920 if (layoutConfig->sizing.width.size.minMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier 1921 layoutConfig->sizing.width.size.minMax.max = CLAY__MAXFLOAT; 1922 } 1923 openLayoutElement->dimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); 1924 openLayoutElement->minDimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); 1925 } else { 1926 openLayoutElement->dimensions.width = 0; 1927 } 1928 1929 // Clamp element min and max height to the values configured in the layout 1930 if (layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { 1931 if (layoutConfig->sizing.height.size.minMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier 1932 layoutConfig->sizing.height.size.minMax.max = CLAY__MAXFLOAT; 1933 } 1934 openLayoutElement->dimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); 1935 openLayoutElement->minDimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); 1936 } else { 1937 openLayoutElement->dimensions.height = 0; 1938 } 1939 1940 Clay__UpdateAspectRatioBox(openLayoutElement); 1941 1942 bool elementIsFloating = openLayoutElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE; 1943 1944 // Close the currently open element 1945 int32_t closingElementIndex = Clay__int32_tArray_RemoveSwapback(&context->openLayoutElementStack, (int)context->openLayoutElementStack.length - 1); 1946 1947 // Get the currently open parent 1948 openLayoutElement = Clay__GetOpenLayoutElement(); 1949 1950 if (context->openLayoutElementStack.length > 1) { 1951 if(elementIsFloating) { 1952 openLayoutElement->floatingChildrenCount++; 1953 return; 1954 } 1955 openLayoutElement->children.length++; 1956 Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, closingElementIndex); 1957 } 1958 } 1959 1960 bool Clay__MemCmp(const char *s1, const char *s2, int32_t length); 1961 #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)) 1962 bool Clay__MemCmp(const char *s1, const char *s2, int32_t length) { 1963 while (length >= 16) { 1964 __m128i v1 = _mm_loadu_si128((const __m128i *)s1); 1965 __m128i v2 = _mm_loadu_si128((const __m128i *)s2); 1966 1967 if (_mm_movemask_epi8(_mm_cmpeq_epi8(v1, v2)) != 0xFFFF) { // If any byte differs 1968 return false; 1969 } 1970 1971 s1 += 16; 1972 s2 += 16; 1973 length -= 16; 1974 } 1975 1976 // Handle remaining bytes 1977 while (length--) { 1978 if (*s1 != *s2) { 1979 return false; 1980 } 1981 s1++; 1982 s2++; 1983 } 1984 1985 return true; 1986 } 1987 #elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__) 1988 bool Clay__MemCmp(const char *s1, const char *s2, int32_t length) { 1989 while (length >= 16) { 1990 uint8x16_t v1 = vld1q_u8((const uint8_t *)s1); 1991 uint8x16_t v2 = vld1q_u8((const uint8_t *)s2); 1992 1993 // Compare vectors 1994 if (vminvq_u32(vreinterpretq_u32_u8(vceqq_u8(v1, v2))) != 0xFFFFFFFF) { // If there's a difference 1995 return false; 1996 } 1997 1998 s1 += 16; 1999 s2 += 16; 2000 length -= 16; 2001 } 2002 2003 // Handle remaining bytes 2004 while (length--) { 2005 if (*s1 != *s2) { 2006 return false; 2007 } 2008 s1++; 2009 s2++; 2010 } 2011 2012 return true; 2013 } 2014 #else 2015 bool Clay__MemCmp(const char *s1, const char *s2, int32_t length) { 2016 for (int32_t i = 0; i < length; i++) { 2017 if (s1[i] != s2[i]) { 2018 return false; 2019 } 2020 } 2021 return true; 2022 } 2023 #endif 2024 2025 void Clay__OpenElement(void) { 2026 Clay_Context* context = Clay_GetCurrentContext(); 2027 if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { 2028 context->booleanWarnings.maxElementsExceeded = true; 2029 return; 2030 } 2031 Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; 2032 Clay_LayoutElement* openLayoutElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); 2033 Clay__int32_tArray_Add(&context->openLayoutElementStack, context->layoutElements.length - 1); 2034 // Generate an ID 2035 Clay_LayoutElement *parentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); 2036 uint32_t offset = parentElement->children.length + parentElement->floatingChildrenCount; 2037 Clay_ElementId elementId = Clay__HashNumber(offset, parentElement->id); 2038 openLayoutElement->id = elementId.id; 2039 Clay__AddHashMapItem(elementId, openLayoutElement); 2040 Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); 2041 if (context->openClipElementStack.length > 0) { 2042 Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); 2043 } else { 2044 Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, 0); 2045 } 2046 } 2047 2048 void Clay__OpenElementWithId(Clay_ElementId elementId) { 2049 Clay_Context* context = Clay_GetCurrentContext(); 2050 if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { 2051 context->booleanWarnings.maxElementsExceeded = true; 2052 return; 2053 } 2054 Clay_LayoutElement layoutElement = CLAY__DEFAULT_STRUCT; 2055 layoutElement.id = elementId.id; 2056 Clay_LayoutElement * openLayoutElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); 2057 Clay__int32_tArray_Add(&context->openLayoutElementStack, context->layoutElements.length - 1); 2058 Clay__AddHashMapItem(elementId, openLayoutElement); 2059 Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); 2060 if (context->openClipElementStack.length > 0) { 2061 Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); 2062 } else { 2063 Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, 0); 2064 } 2065 } 2066 2067 void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig textConfig) { 2068 Clay_Context* context = Clay_GetCurrentContext(); 2069 if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) { 2070 context->booleanWarnings.maxElementsExceeded = true; 2071 return; 2072 } 2073 Clay_LayoutElement *parentElement = Clay__GetOpenLayoutElement(); 2074 2075 Clay_LayoutElement layoutElement = { .textConfig = textConfig, .isTextElement = true }; 2076 Clay_LayoutElement *textElement = Clay_LayoutElementArray_Add(&context->layoutElements, layoutElement); 2077 if (context->openClipElementStack.length > 0) { 2078 Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1)); 2079 } else { 2080 Clay__int32_tArray_Set(&context->layoutElementClipElementIds, context->layoutElements.length - 1, 0); 2081 } 2082 2083 Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, context->layoutElements.length - 1); 2084 Clay__MeasureTextCacheItem *textMeasured = Clay__MeasureTextCached(&text, &textConfig); 2085 Clay_ElementId elementId = Clay__HashNumber(parentElement->children.length + parentElement->floatingChildrenCount, parentElement->id); 2086 textElement->id = elementId.id; 2087 Clay__AddHashMapItem(elementId, textElement); 2088 Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId); 2089 Clay_Dimensions textDimensions = { .width = textMeasured->unwrappedDimensions.width, .height = textConfig.lineHeight > 0 ? (float)textConfig.lineHeight : textMeasured->unwrappedDimensions.height }; 2090 textElement->dimensions = textDimensions; 2091 textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->minWidth, .height = textDimensions.height }; 2092 textElement->textElementData = CLAY__INIT(Clay__TextElementData) { .text = text, .preferredDimensions = textMeasured->unwrappedDimensions }; 2093 parentElement->children.length++; 2094 } 2095 2096 void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) { 2097 Clay_Context* context = Clay_GetCurrentContext(); 2098 Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); 2099 openLayoutElement->config = *declaration; 2100 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)) { 2101 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 2102 .errorType = CLAY_ERROR_TYPE_PERCENTAGE_OVER_1, 2103 .errorText = CLAY_STRING("An element was configured with CLAY_SIZING_PERCENT, but the provided percentage value was over 1.0. Clay expects a value between 0 and 1, i.e. 20% is 0.2."), 2104 .userData = context->errorHandler.userData }); 2105 } 2106 2107 if (declaration->floating.attachTo != CLAY_ATTACH_TO_NONE) { 2108 Clay_FloatingElementConfig* floatingConfig = &openLayoutElement->config.floating; 2109 // This looks dodgy but because of the auto generated root element the depth of the tree will always be at least 2 here 2110 Clay_LayoutElement *hierarchicalParent = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2)); 2111 if (hierarchicalParent) { 2112 uint32_t clipElementId = 0; 2113 if (declaration->floating.attachTo == CLAY_ATTACH_TO_PARENT) { 2114 // Attach to the element's direct hierarchical parent 2115 floatingConfig->parentId = hierarchicalParent->id; 2116 if (context->openClipElementStack.length > 0) { 2117 clipElementId = Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1); 2118 } 2119 } else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) { 2120 Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingConfig->parentId); 2121 if (parentItem == &Clay_LayoutElementHashMapItem_DEFAULT) { 2122 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 2123 .errorType = CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, 2124 .errorText = CLAY_STRING("A floating element was declared with a parentId, but no element with that ID was found."), 2125 .userData = context->errorHandler.userData }); 2126 } else { 2127 clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(parentItem->layoutElement - context->layoutElements.internalArray)); 2128 } 2129 } else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ROOT) { 2130 floatingConfig->parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0).id; 2131 } 2132 if (declaration->floating.clipTo == CLAY_CLIP_TO_NONE) { 2133 clipElementId = 0; 2134 } 2135 int32_t currentElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1); 2136 Clay__int32_tArray_Set(&context->layoutElementClipElementIds, currentElementIndex, clipElementId); 2137 Clay__int32_tArray_Add(&context->openClipElementStack, clipElementId); 2138 Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { 2139 .layoutElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1), 2140 .parentId = floatingConfig->parentId, 2141 .clipElementId = clipElementId, 2142 .zIndex = floatingConfig->zIndex, 2143 }); 2144 } 2145 } 2146 2147 if (declaration->clip.horizontal || declaration->clip.vertical) { 2148 Clay__int32_tArray_Add(&context->openClipElementStack, (int)openLayoutElement->id); 2149 // Retrieve or create cached data to track scroll position across frames 2150 Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL; 2151 for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { 2152 Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); 2153 if (openLayoutElement->id == mapping->elementId) { 2154 scrollOffset = mapping; 2155 scrollOffset->layoutElement = openLayoutElement; 2156 scrollOffset->openThisFrame = true; 2157 } 2158 } 2159 if (!scrollOffset) { 2160 scrollOffset = Clay__ScrollContainerDataInternalArray_Add(&context->scrollContainerDatas, CLAY__INIT(Clay__ScrollContainerDataInternal){.layoutElement = openLayoutElement, .scrollOrigin = {-1,-1}, .elementId = openLayoutElement->id, .openThisFrame = true}); 2161 } 2162 if (context->externalScrollHandlingEnabled) { 2163 scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId, context->queryScrollOffsetUserData); 2164 } 2165 } 2166 // Setup data to track transitions across frames 2167 if (declaration->transition.handler) { 2168 Clay__TransitionDataInternal *transitionData = CLAY__NULL; 2169 Clay_LayoutElement* parentElement = Clay__GetParentElement(); 2170 for (int32_t i = 0; i < context->transitionDatas.length; i++) { 2171 Clay__TransitionDataInternal *existingData = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, i); 2172 if (openLayoutElement->id == existingData->elementId) { 2173 if (existingData->state == CLAY_TRANSITION_STATE_EXITING) { 2174 existingData->state = CLAY_TRANSITION_STATE_IDLE; 2175 Clay_LayoutElementHashMapItem* hashMapItem = Clay__GetHashMapItem(openLayoutElement->id); 2176 hashMapItem->appearedThisFrame = false; 2177 } 2178 transitionData = existingData; 2179 transitionData->elementThisFrame = openLayoutElement; 2180 if (transitionData->parentId != parentElement->id) { 2181 transitionData->reparented = true; 2182 } 2183 transitionData->parentId = parentElement->id; 2184 transitionData->siblingIndex = parentElement->children.length; 2185 transitionData->transitionOut = !!declaration->transition.exit.setFinalState; 2186 } 2187 } 2188 if (!transitionData) { 2189 transitionData = Clay__TransitionDataInternalArray_Add(&context->transitionDatas, CLAY__INIT(Clay__TransitionDataInternal){ 2190 .elementThisFrame = openLayoutElement, 2191 .elementId = openLayoutElement->id, 2192 .parentId = parentElement->id, 2193 .siblingIndex = parentElement->children.length, 2194 .transitionOut = !!declaration->transition.exit.setFinalState 2195 }); 2196 } 2197 } 2198 } 2199 2200 void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) { 2201 Clay__ConfigureOpenElementPtr(&declaration); 2202 } 2203 2204 void Clay__InitializeEphemeralMemory(Clay_Context* context) { 2205 int32_t maxElementCount = context->maxElementCount; 2206 // Ephemeral Memory - reset every frame 2207 Clay_Arena *arena = &context->internalArena; 2208 arena->nextAllocation = context->arenaResetOffset; 2209 2210 context->layoutElementChildrenBuffer = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); 2211 context->layoutElements = Clay_LayoutElementArray_Allocate_Arena(maxElementCount, arena); 2212 context->warnings = Clay__WarningArray_Allocate_Arena(100, arena); 2213 2214 context->layoutElementIdStrings = Clay__StringArray_Allocate_Arena(maxElementCount, arena); 2215 context->wrappedTextLines = Clay__WrappedTextLineArray_Allocate_Arena(maxElementCount, arena); 2216 context->layoutElementTreeNodeArray1 = Clay__LayoutElementTreeNodeArray_Allocate_Arena(maxElementCount, arena); 2217 context->layoutElementTreeRoots = Clay__LayoutElementTreeRootArray_Allocate_Arena(maxElementCount, arena); 2218 context->layoutElementChildren = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); 2219 context->openLayoutElementStack = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); 2220 context->renderCommands = Clay_RenderCommandArray_Allocate_Arena(maxElementCount, arena); 2221 context->treeNodeVisited = Clay__boolArray_Allocate_Arena(maxElementCount, arena); 2222 context->treeNodeVisited.length = context->treeNodeVisited.capacity; // This array is accessed directly rather than behaving as a list 2223 context->openClipElementStack = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); 2224 context->reusableElementIndexBuffer = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); 2225 context->layoutElementClipElementIds = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); 2226 context->dynamicStringData = Clay__charArray_Allocate_Arena(maxElementCount, arena); 2227 } 2228 2229 void Clay__InitializePersistentMemory(Clay_Context* context) { 2230 // Persistent memory - initialized once and not reset 2231 int32_t maxElementCount = context->maxElementCount; 2232 int32_t maxMeasureTextCacheWordCount = context->maxMeasureTextCacheWordCount; 2233 Clay_Arena *arena = &context->internalArena; 2234 2235 context->scrollContainerDatas = Clay__ScrollContainerDataInternalArray_Allocate_Arena(100, arena); 2236 context->transitionDatas = Clay__TransitionDataInternalArray_Allocate_Arena(200, arena); 2237 context->layoutElementsHashMapInternal = Clay__LayoutElementHashMapItemArray_Allocate_Arena(maxElementCount, arena); 2238 context->layoutElementsHashMap = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); 2239 context->measureTextHashMapInternal = Clay__MeasureTextCacheItemArray_Allocate_Arena(maxElementCount, arena); 2240 context->measureTextHashMapInternalFreeList = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); 2241 context->measuredWordsFreeList = Clay__int32_tArray_Allocate_Arena(maxMeasureTextCacheWordCount, arena); 2242 context->measureTextHashMap = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); 2243 context->measuredWords = Clay__MeasuredWordArray_Allocate_Arena(maxMeasureTextCacheWordCount, arena); 2244 context->pointerOverIds = Clay_ElementIdArray_Allocate_Arena(maxElementCount, arena); 2245 context->debugElementData = Clay__DebugElementDataArray_Allocate_Arena(maxElementCount, arena); 2246 context->arenaResetOffset = arena->nextAllocation; 2247 } 2248 2249 const float CLAY__EPSILON = 0.01; 2250 2251 bool Clay__FloatEqual(float left, float right) { 2252 float subtracted = left - right; 2253 return subtracted < CLAY__EPSILON && subtracted > -CLAY__EPSILON; 2254 } 2255 2256 Clay_SizingAxis Clay__GetElementSizing(Clay_LayoutElement* element, bool xAxis) { 2257 if (element->isTextElement) { 2258 return CLAY__INIT(Clay_SizingAxis) {}; 2259 } else { 2260 return xAxis ? element->config.layout.sizing.width : element->config.layout.sizing.height; 2261 } 2262 } 2263 2264 // Writes out the location of text elements to layout elements buffer 1 2265 void Clay__SizeContainersAlongAxis(bool xAxis, float deltaTime, Clay__int32_tArray* textElementsOut, Clay__int32_tArray* aspectRatioElementsOut) { 2266 Clay_Context* context = Clay_GetCurrentContext(); 2267 Clay__int32_tArray bfsBuffer = context->layoutElementChildrenBuffer; 2268 Clay__int32_tArray resizableContainerBuffer = context->openLayoutElementStack; 2269 for (int32_t rootIndex = 0; rootIndex < context->layoutElementTreeRoots.length; ++rootIndex) { 2270 bfsBuffer.length = 0; 2271 Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); 2272 Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)root->layoutElementIndex); 2273 Clay__int32_tArray_Add(&bfsBuffer, (int32_t)root->layoutElementIndex); 2274 2275 // Size floating containers to their parents 2276 if (rootElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { 2277 Clay_FloatingElementConfig *floatingElementConfig = &rootElement->config.floating; 2278 Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingElementConfig->parentId); 2279 if (parentItem && parentItem != &Clay_LayoutElementHashMapItem_DEFAULT) { 2280 Clay_LayoutElement *parentLayoutElement = parentItem->layoutElement; 2281 switch (rootElement->config.layout.sizing.width.type) { 2282 case CLAY__SIZING_TYPE_GROW: { 2283 rootElement->dimensions.width = parentLayoutElement->dimensions.width; 2284 break; 2285 } 2286 case CLAY__SIZING_TYPE_PERCENT: { 2287 rootElement->dimensions.width = parentLayoutElement->dimensions.width * rootElement->config.layout.sizing.width.size.percent; 2288 break; 2289 } 2290 default: break; 2291 } 2292 switch (rootElement->config.layout.sizing.height.type) { 2293 case CLAY__SIZING_TYPE_GROW: { 2294 rootElement->dimensions.height = parentLayoutElement->dimensions.height; 2295 break; 2296 } 2297 case CLAY__SIZING_TYPE_PERCENT: { 2298 rootElement->dimensions.height = parentLayoutElement->dimensions.height * rootElement->config.layout.sizing.height.size.percent; 2299 break; 2300 } 2301 default: break; 2302 } 2303 } 2304 } 2305 2306 if (rootElement->config.layout.sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { 2307 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); 2308 } 2309 if (rootElement->config.layout.sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { 2310 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); 2311 } 2312 2313 2314 for (int32_t i = 0; i < bfsBuffer.length; ++i) { 2315 int32_t parentIndex = Clay__int32_tArray_GetValue(&bfsBuffer, i); 2316 Clay_LayoutElement *parent = Clay_LayoutElementArray_Get(&context->layoutElements, parentIndex); 2317 Clay_LayoutConfig *parentLayoutConfig = &parent->config.layout; 2318 int32_t growContainerCount = 0; 2319 float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height; 2320 float parentPadding = (float)(xAxis ? (parentLayoutConfig->padding.left + parentLayoutConfig->padding.right) : (parentLayoutConfig->padding.top + parentLayoutConfig->padding.bottom)); 2321 float innerContentSize = 0, totalPaddingAndChildGaps = parentPadding; 2322 bool sizingAlongAxis = (xAxis && parentLayoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentLayoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM); 2323 resizableContainerBuffer.length = 0; 2324 float parentChildGap = parentLayoutConfig->childGap; 2325 bool isFirstChild = true; 2326 2327 for (int32_t childOffset = 0; childOffset < parent->children.length; childOffset++) { 2328 int32_t childElementIndex = parent->children.elements[childOffset]; 2329 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childElementIndex); 2330 Clay_SizingAxis childSizing = Clay__GetElementSizing(childElement, xAxis); 2331 float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height; 2332 2333 if (textElementsOut && childElement->isTextElement) { 2334 Clay__int32_tArray_Add(textElementsOut, childElementIndex); 2335 } else if (childElement->children.length > 0) { 2336 Clay__int32_tArray_Add(&bfsBuffer, childElementIndex); 2337 } 2338 2339 if (!childElement->isTextElement && aspectRatioElementsOut && childElement->config.aspectRatio.aspectRatio != 0) { 2340 Clay__int32_tArray_Add(aspectRatioElementsOut, childElementIndex); 2341 } 2342 2343 // Note: setting isFirstChild = false is skipped here 2344 if (childElement->exiting) { 2345 continue; 2346 } 2347 2348 if (childSizing.type != CLAY__SIZING_TYPE_PERCENT 2349 && childSizing.type != CLAY__SIZING_TYPE_FIXED 2350 && (!childElement->isTextElement || childElement->textConfig.wrapMode == CLAY_TEXT_WRAP_WORDS) 2351 // && (xAxis || !Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT)) 2352 ) { 2353 Clay__int32_tArray_Add(&resizableContainerBuffer, childElementIndex); 2354 } 2355 2356 if (sizingAlongAxis) { 2357 innerContentSize += (childSizing.type == CLAY__SIZING_TYPE_PERCENT ? 0 : childSize); 2358 if (childSizing.type == CLAY__SIZING_TYPE_GROW) { 2359 growContainerCount++; 2360 } 2361 if (!isFirstChild) { 2362 innerContentSize += parentChildGap; // For children after index 0, the childAxisOffset is the gap from the previous child 2363 totalPaddingAndChildGaps += parentChildGap; 2364 } 2365 } else { 2366 innerContentSize = CLAY__MAX(childSize, innerContentSize); 2367 } 2368 isFirstChild = false; 2369 } 2370 2371 // Expand percentage containers to size 2372 for (int32_t childOffset = 0; childOffset < parent->children.length; childOffset++) { 2373 int32_t childElementIndex = parent->children.elements[childOffset]; 2374 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childElementIndex); 2375 Clay_SizingAxis childSizing = Clay__GetElementSizing(childElement, xAxis); 2376 float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; 2377 if (childSizing.type == CLAY__SIZING_TYPE_PERCENT) { 2378 *childSize = (parentSize - totalPaddingAndChildGaps) * childSizing.size.percent; 2379 if (sizingAlongAxis) { 2380 innerContentSize += *childSize; 2381 } 2382 Clay__UpdateAspectRatioBox(childElement); 2383 } 2384 } 2385 2386 if (sizingAlongAxis) { 2387 float sizeToDistribute = parentSize - parentPadding - innerContentSize; 2388 // The content is too large, compress the children as much as possible 2389 if (sizeToDistribute < 0) { 2390 // If the parent clips content in this axis direction, don't compress children, just leave them alone 2391 if (((xAxis && parent->config.clip.horizontal) || (!xAxis && parent->config.clip.vertical))) { 2392 continue; 2393 } 2394 // Scrolling containers preferentially compress before others 2395 while (sizeToDistribute < -CLAY__EPSILON && resizableContainerBuffer.length > 0) { 2396 float largest = 0; 2397 float secondLargest = 0; 2398 float widthToAdd = sizeToDistribute; 2399 for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { 2400 Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); 2401 float childSize = xAxis ? child->dimensions.width : child->dimensions.height; 2402 if (Clay__FloatEqual(childSize, largest)) { continue; } 2403 if (childSize > largest) { 2404 secondLargest = largest; 2405 largest = childSize; 2406 } 2407 if (childSize < largest) { 2408 secondLargest = CLAY__MAX(secondLargest, childSize); 2409 widthToAdd = secondLargest - largest; 2410 } 2411 } 2412 2413 widthToAdd = CLAY__MAX(widthToAdd, sizeToDistribute / resizableContainerBuffer.length); 2414 2415 for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { 2416 Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); 2417 float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height; 2418 float minSize = xAxis ? child->minDimensions.width : child->minDimensions.height; 2419 float previousWidth = *childSize; 2420 if (Clay__FloatEqual(*childSize, largest)) { 2421 *childSize += widthToAdd; 2422 if (*childSize <= minSize) { 2423 *childSize = minSize; 2424 Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); 2425 } 2426 sizeToDistribute -= (*childSize - previousWidth); 2427 } 2428 } 2429 } 2430 // The content is too small, allow SIZING_GROW containers to expand 2431 } else if (sizeToDistribute > 0 && growContainerCount > 0) { 2432 for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { 2433 Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); 2434 Clay__SizingType childSizing = Clay__GetElementSizing(child, xAxis).type; 2435 if (childSizing != CLAY__SIZING_TYPE_GROW) { 2436 Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); 2437 } 2438 } 2439 while (sizeToDistribute > CLAY__EPSILON && resizableContainerBuffer.length > 0) { 2440 float smallest = CLAY__MAXFLOAT; 2441 float secondSmallest = CLAY__MAXFLOAT; 2442 float widthToAdd = sizeToDistribute; 2443 for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { 2444 Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); 2445 float childSize = xAxis ? child->dimensions.width : child->dimensions.height; 2446 if (Clay__FloatEqual(childSize, smallest)) { continue; } 2447 if (childSize < smallest) { 2448 secondSmallest = smallest; 2449 smallest = childSize; 2450 } 2451 if (childSize > smallest) { 2452 secondSmallest = CLAY__MIN(secondSmallest, childSize); 2453 widthToAdd = secondSmallest - smallest; 2454 } 2455 } 2456 2457 widthToAdd = CLAY__MIN(widthToAdd, sizeToDistribute / resizableContainerBuffer.length); 2458 2459 for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) { 2460 Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex)); 2461 float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height; 2462 Clay_SizingAxis childSizing = Clay__GetElementSizing(child, xAxis); 2463 float maxSize = childSizing.size.minMax.max; 2464 float previousWidth = *childSize; 2465 if (Clay__FloatEqual(*childSize, smallest)) { 2466 *childSize += widthToAdd; 2467 if (*childSize >= maxSize) { 2468 *childSize = maxSize; 2469 Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--); 2470 } 2471 sizeToDistribute -= (*childSize - previousWidth); 2472 } 2473 } 2474 } 2475 } 2476 // Sizing along the non layout axis ("off axis") 2477 } else { 2478 for (int32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) { 2479 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childOffset)); 2480 Clay_SizingAxis childSizing = Clay__GetElementSizing(childElement, xAxis); 2481 float minSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height; 2482 float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; 2483 2484 float maxSize = parentSize - parentPadding; 2485 // 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 2486 if (((xAxis && parent->config.clip.horizontal) || (!xAxis && parent->config.clip.vertical))) { 2487 maxSize = CLAY__MAX(maxSize, innerContentSize); 2488 } 2489 if (childSizing.type == CLAY__SIZING_TYPE_GROW) { 2490 *childSize = CLAY__MIN(maxSize, childSizing.size.minMax.max); 2491 } 2492 *childSize = CLAY__MAX(minSize, CLAY__MIN(*childSize, maxSize)); 2493 } 2494 } 2495 } 2496 } 2497 } 2498 2499 Clay_String Clay__IntToString(int32_t integer) { 2500 if (integer == 0) { 2501 return CLAY__INIT(Clay_String) { .length = 1, .chars = "0" }; 2502 } 2503 Clay_Context* context = Clay_GetCurrentContext(); 2504 char *chars = (char *)(context->dynamicStringData.internalArray + context->dynamicStringData.length); 2505 int32_t length = 0; 2506 int32_t sign = integer; 2507 2508 if (integer < 0) { 2509 integer = -integer; 2510 } 2511 while (integer > 0) { 2512 chars[length++] = (char)(integer % 10 + '0'); 2513 integer /= 10; 2514 } 2515 2516 if (sign < 0) { 2517 chars[length++] = '-'; 2518 } 2519 2520 // Reverse the string to get the correct order 2521 for (int32_t j = 0, k = length - 1; j < k; j++, k--) { 2522 char temp = chars[j]; 2523 chars[j] = chars[k]; 2524 chars[k] = temp; 2525 } 2526 context->dynamicStringData.length += length; 2527 return CLAY__INIT(Clay_String) { .length = length, .chars = chars }; 2528 } 2529 2530 void Clay__AddRenderCommand(Clay_RenderCommand renderCommand) { 2531 Clay_Context* context = Clay_GetCurrentContext(); 2532 if (context->renderCommands.length < context->renderCommands.capacity - 1) { 2533 Clay_RenderCommandArray_Add(&context->renderCommands, renderCommand); 2534 } else { 2535 if (!context->booleanWarnings.maxRenderCommandsExceeded) { 2536 context->booleanWarnings.maxRenderCommandsExceeded = true; 2537 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 2538 .errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, 2539 .errorText = CLAY_STRING("Clay ran out of capacity while attempting to create render commands. This is usually caused by a large amount of wrapping text elements while close to the max element capacity. Try using Clay_SetMaxElementCount() with a higher value."), 2540 .userData = context->errorHandler.userData }); 2541 } 2542 } 2543 } 2544 2545 bool Clay__ElementIsOffscreen(Clay_BoundingBox *boundingBox) { 2546 Clay_Context* context = Clay_GetCurrentContext(); 2547 if (context->disableCulling) { 2548 return false; 2549 } 2550 2551 return (boundingBox->x > (float)context->layoutDimensions.width) || 2552 (boundingBox->y > (float)context->layoutDimensions.height) || 2553 (boundingBox->x + boundingBox->width < 0) || 2554 (boundingBox->y + boundingBox->height < 0); 2555 } 2556 2557 void Clay__CalculateFinalLayout(float deltaTime, bool useStoredBoundingBoxes, bool generateRenderCommands) { 2558 Clay_Context* context = Clay_GetCurrentContext(); 2559 2560 // Calculate sizing along the X axis 2561 Clay__int32_tArray textElements = context->openClipElementStack; 2562 textElements.length = 0; 2563 Clay__int32_tArray aspectRatioElements = context->reusableElementIndexBuffer; 2564 aspectRatioElements.length = 0; 2565 Clay__SizeContainersAlongAxis(true, deltaTime, &textElements, &aspectRatioElements); 2566 2567 // Wrap text 2568 for (int32_t textElementIndex = 0; textElementIndex < textElements.length; ++textElementIndex) { 2569 Clay_LayoutElement *element = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&textElements, textElementIndex)); 2570 Clay__TextElementData *textElementData = &element->textElementData; 2571 textElementData->wrappedLines = CLAY__INIT(Clay__WrappedTextLineArraySlice) { .length = 0, .internalArray = &context->wrappedTextLines.internalArray[context->wrappedTextLines.length] }; 2572 Clay_LayoutElement *containerElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&textElements, textElementIndex)); 2573 Clay__MeasureTextCacheItem *measureTextCacheItem = Clay__MeasureTextCached(&textElementData->text, &containerElement->textConfig); 2574 float lineWidth = 0; 2575 float lineHeight = containerElement->textConfig.lineHeight > 0 ? (float)containerElement->textConfig.lineHeight : textElementData->preferredDimensions.height; 2576 int32_t lineLengthChars = 0; 2577 int32_t lineStartOffset = 0; 2578 if (!measureTextCacheItem->containsNewlines && textElementData->preferredDimensions.width <= containerElement->dimensions.width) { 2579 Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { containerElement->dimensions, textElementData->text }); 2580 textElementData->wrappedLines.length++; 2581 continue; 2582 } 2583 float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, &containerElement->textConfig, context->measureTextUserData).width; 2584 int32_t wordIndex = measureTextCacheItem->measuredWordsStartIndex; 2585 while (wordIndex != -1) { 2586 if (context->wrappedTextLines.length > context->wrappedTextLines.capacity - 1) { 2587 break; 2588 } 2589 Clay__MeasuredWord *measuredWord = Clay__MeasuredWordArray_Get(&context->measuredWords, wordIndex); 2590 // Only word on the line is too large, just render it anyway 2591 if (lineLengthChars == 0 && lineWidth + measuredWord->width > containerElement->dimensions.width) { 2592 Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { measuredWord->width, lineHeight }, { .length = measuredWord->length, .chars = &textElementData->text.chars[measuredWord->startOffset] } }); 2593 textElementData->wrappedLines.length++; 2594 wordIndex = measuredWord->next; 2595 lineStartOffset = measuredWord->startOffset + measuredWord->length; 2596 } 2597 // measuredWord->length == 0 means a newline character 2598 else if (measuredWord->length == 0 || lineWidth + measuredWord->width > containerElement->dimensions.width) { 2599 // Wrapped text lines list has overflowed, just render out the line 2600 bool finalCharIsSpace = textElementData->text.chars[CLAY__MAX(lineStartOffset + lineLengthChars - 1, 0)] == ' '; 2601 Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth + (finalCharIsSpace ? -spaceWidth : 0), lineHeight }, { .length = lineLengthChars + (finalCharIsSpace ? -1 : 0), .chars = &textElementData->text.chars[lineStartOffset] } }); 2602 textElementData->wrappedLines.length++; 2603 if (lineLengthChars == 0 || measuredWord->length == 0) { 2604 wordIndex = measuredWord->next; 2605 } 2606 lineWidth = 0; 2607 lineLengthChars = 0; 2608 lineStartOffset = measuredWord->startOffset; 2609 } else { 2610 lineWidth += measuredWord->width + containerElement->textConfig.letterSpacing; 2611 lineLengthChars += measuredWord->length; 2612 wordIndex = measuredWord->next; 2613 } 2614 } 2615 if (lineLengthChars > 0) { 2616 Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth - containerElement->textConfig.letterSpacing, lineHeight }, {.length = lineLengthChars, .chars = &textElementData->text.chars[lineStartOffset] } }); 2617 textElementData->wrappedLines.length++; 2618 } 2619 containerElement->dimensions.height = lineHeight * (float)textElementData->wrappedLines.length; 2620 } 2621 2622 // Scale vertical heights according to aspect ratio 2623 for (int32_t i = 0; i < aspectRatioElements.length; ++i) { 2624 Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&aspectRatioElements, i)); 2625 aspectElement->dimensions.height = (1 / aspectElement->config.aspectRatio.aspectRatio) * aspectElement->dimensions.width; 2626 aspectElement->config.layout.sizing.height.size.minMax.max = aspectElement->dimensions.height; 2627 } 2628 2629 // Propagate effect of text wrapping, aspect scaling etc. on height of parents 2630 Clay__LayoutElementTreeNodeArray dfsBuffer = context->layoutElementTreeNodeArray1; 2631 dfsBuffer.length = 0; 2632 for (int32_t i = 0; i < context->layoutElementTreeRoots.length; ++i) { 2633 Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, i); 2634 context->treeNodeVisited.internalArray[dfsBuffer.length] = false; 2635 Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)root->layoutElementIndex) }); 2636 } 2637 while (dfsBuffer.length > 0) { 2638 Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); 2639 Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; 2640 if (!context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { 2641 context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; 2642 // If the element has no children or is the container for a text element, don't bother inspecting it 2643 if (currentElement->isTextElement || currentElement->children.length == 0) { 2644 dfsBuffer.length--; 2645 continue; 2646 } 2647 // Add the children to the DFS buffer (needs to be pushed in reverse so that stack traversal is in correct layout order) 2648 for (int32_t i = 0; i < currentElement->children.length; i++) { 2649 context->treeNodeVisited.internalArray[dfsBuffer.length] = false; 2650 Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]) }); 2651 } 2652 continue; 2653 } 2654 dfsBuffer.length--; 2655 2656 // DFS node has been visited, this is on the way back up to the root 2657 Clay_LayoutConfig *layoutConfig = ¤tElement->config.layout; 2658 if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { 2659 // Resize any parent containers that have grown in height along their non layout axis 2660 for (int32_t j = 0; j < currentElement->children.length; ++j) { 2661 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[j]); 2662 float childHeightWithPadding = CLAY__MAX(childElement->dimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom, currentElement->dimensions.height); 2663 currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(childHeightWithPadding, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); 2664 } 2665 } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { 2666 // Resizing along the layout axis 2667 float contentHeight = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom); 2668 for (int32_t j = 0; j < currentElement->children.length; ++j) { 2669 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[j]); 2670 contentHeight += childElement->dimensions.height; 2671 } 2672 contentHeight += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap); 2673 currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(contentHeight, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); 2674 } 2675 } 2676 2677 // Calculate sizing along the Y axis 2678 Clay__SizeContainersAlongAxis(false, deltaTime, NULL, NULL); 2679 2680 // Scale horizontal widths according to aspect ratio 2681 for (int32_t i = 0; i < aspectRatioElements.length; ++i) { 2682 Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&aspectRatioElements, i)); 2683 aspectElement->dimensions.width = aspectElement->config.aspectRatio.aspectRatio * aspectElement->dimensions.height; 2684 } 2685 2686 // Sort tree roots by z-index 2687 int32_t sortMax = context->layoutElementTreeRoots.length - 1; 2688 while (sortMax > 0) { // todo dumb bubble sort 2689 for (int32_t i = 0; i < sortMax; ++i) { 2690 Clay__LayoutElementTreeRoot current = *Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, i); 2691 Clay__LayoutElementTreeRoot next = *Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, i + 1); 2692 if (next.zIndex < current.zIndex) { 2693 Clay__LayoutElementTreeRootArray_Set(&context->layoutElementTreeRoots, i, next); 2694 Clay__LayoutElementTreeRootArray_Set(&context->layoutElementTreeRoots, i + 1, current); 2695 } 2696 } 2697 sortMax--; 2698 } 2699 2700 // Calculate final positions and generate render commands 2701 context->renderCommands.length = 0; 2702 dfsBuffer.length = 0; 2703 2704 for (int32_t rootIndex = 0; rootIndex < context->layoutElementTreeRoots.length; ++rootIndex) { 2705 dfsBuffer.length = 0; 2706 Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); 2707 Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)root->layoutElementIndex); 2708 Clay_Vector2 rootPosition = CLAY__DEFAULT_STRUCT; 2709 Clay_LayoutElementHashMapItem *parentHashMapItem = Clay__GetHashMapItem(root->parentId); 2710 // Position root floating containers 2711 if (rootElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE && parentHashMapItem) { 2712 Clay_FloatingElementConfig *config = &rootElement->config.floating; 2713 Clay_Dimensions rootDimensions = rootElement->dimensions; 2714 Clay_BoundingBox parentBoundingBox = parentHashMapItem->boundingBox; 2715 // Set X position 2716 Clay_Vector2 targetAttachPosition = CLAY__DEFAULT_STRUCT; 2717 switch (config->attachPoints.parent) { 2718 case CLAY_ATTACH_POINT_LEFT_TOP: 2719 case CLAY_ATTACH_POINT_LEFT_CENTER: 2720 case CLAY_ATTACH_POINT_LEFT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x; break; 2721 case CLAY_ATTACH_POINT_CENTER_TOP: 2722 case CLAY_ATTACH_POINT_CENTER_CENTER: 2723 case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + (parentBoundingBox.width / 2); break; 2724 case CLAY_ATTACH_POINT_RIGHT_TOP: 2725 case CLAY_ATTACH_POINT_RIGHT_CENTER: 2726 case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + parentBoundingBox.width; break; 2727 } 2728 switch (config->attachPoints.element) { 2729 case CLAY_ATTACH_POINT_LEFT_TOP: 2730 case CLAY_ATTACH_POINT_LEFT_CENTER: 2731 case CLAY_ATTACH_POINT_LEFT_BOTTOM: break; 2732 case CLAY_ATTACH_POINT_CENTER_TOP: 2733 case CLAY_ATTACH_POINT_CENTER_CENTER: 2734 case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x -= (rootDimensions.width / 2); break; 2735 case CLAY_ATTACH_POINT_RIGHT_TOP: 2736 case CLAY_ATTACH_POINT_RIGHT_CENTER: 2737 case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x -= rootDimensions.width; break; 2738 } 2739 switch (config->attachPoints.parent) { // I know I could merge the x and y switch statements, but this is easier to read 2740 case CLAY_ATTACH_POINT_LEFT_TOP: 2741 case CLAY_ATTACH_POINT_RIGHT_TOP: 2742 case CLAY_ATTACH_POINT_CENTER_TOP: targetAttachPosition.y = parentBoundingBox.y; break; 2743 case CLAY_ATTACH_POINT_LEFT_CENTER: 2744 case CLAY_ATTACH_POINT_CENTER_CENTER: 2745 case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y = parentBoundingBox.y + (parentBoundingBox.height / 2); break; 2746 case CLAY_ATTACH_POINT_LEFT_BOTTOM: 2747 case CLAY_ATTACH_POINT_CENTER_BOTTOM: 2748 case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y = parentBoundingBox.y + parentBoundingBox.height; break; 2749 } 2750 switch (config->attachPoints.element) { 2751 case CLAY_ATTACH_POINT_LEFT_TOP: 2752 case CLAY_ATTACH_POINT_RIGHT_TOP: 2753 case CLAY_ATTACH_POINT_CENTER_TOP: break; 2754 case CLAY_ATTACH_POINT_LEFT_CENTER: 2755 case CLAY_ATTACH_POINT_CENTER_CENTER: 2756 case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y -= (rootDimensions.height / 2); break; 2757 case CLAY_ATTACH_POINT_LEFT_BOTTOM: 2758 case CLAY_ATTACH_POINT_CENTER_BOTTOM: 2759 case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y -= rootDimensions.height; break; 2760 } 2761 targetAttachPosition.x += config->offset.x; 2762 targetAttachPosition.y += config->offset.y; 2763 rootPosition = targetAttachPosition; 2764 } 2765 if (root->clipElementId) { 2766 Clay_LayoutElementHashMapItem *clipHashMapItem = Clay__GetHashMapItem(root->clipElementId); 2767 if (clipHashMapItem && !Clay__ElementIsOffscreen(&clipHashMapItem->boundingBox)) { 2768 // Floating elements that are attached to scrolling contents won't be correctly positioned if external scroll handling is enabled, fix here 2769 if (context->externalScrollHandlingEnabled) { 2770 if (clipHashMapItem->layoutElement->config.clip.horizontal) { 2771 rootPosition.x += clipHashMapItem->layoutElement->config.clip.childOffset.x; 2772 } 2773 if (clipHashMapItem->layoutElement->config.clip.vertical) { 2774 rootPosition.y += clipHashMapItem->layoutElement->config.clip.childOffset.y; 2775 } 2776 } 2777 if (generateRenderCommands) { 2778 Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { 2779 .boundingBox = clipHashMapItem->boundingBox, 2780 .userData = 0, 2781 .id = Clay__HashNumber(rootElement->id, rootElement->children.length + 10).id, // TODO need a better strategy for managing derived ids 2782 .zIndex = root->zIndex, 2783 .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, 2784 }); 2785 } 2786 } 2787 } 2788 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 } }); 2789 2790 context->treeNodeVisited.internalArray[0] = false; 2791 while (dfsBuffer.length > 0) { 2792 Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); 2793 Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; 2794 Clay_LayoutConfig *layoutConfig = currentElement->isTextElement ? &CLAY_LAYOUT_DEFAULT : ¤tElement->config.layout; 2795 Clay_Vector2 scrollOffset = CLAY__DEFAULT_STRUCT; 2796 2797 // DFS is returning back upwards 2798 if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { 2799 if (currentElement->isTextElement) { 2800 dfsBuffer.length--; 2801 continue; 2802 } 2803 Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); 2804 if (generateRenderCommands && !Clay__ElementIsOffscreen(¤tElementData->boundingBox)) { 2805 // DFS is returning upwards backwards 2806 bool closeClipElement = false; 2807 if (currentElement->config.clip.horizontal || currentElement->config.clip.vertical) { 2808 closeClipElement = true; 2809 for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { 2810 Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); 2811 if (mapping->layoutElement == currentElement) { 2812 scrollOffset = currentElement->config.clip.childOffset; 2813 if (context->externalScrollHandlingEnabled) { 2814 scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; 2815 } 2816 break; 2817 } 2818 } 2819 } 2820 2821 if (Clay__BorderHasAnyWidth(¤tElement->config.border)) { 2822 Clay_BoundingBox currentElementBoundingBox = currentElementData->boundingBox; 2823 Clay_BorderElementConfig *borderConfig = ¤tElement->config.border; 2824 Clay_RenderCommand renderCommand = { 2825 .boundingBox = currentElementBoundingBox, 2826 .renderData = { .border = { 2827 .color = borderConfig->color, 2828 .cornerRadius = currentElement->config.cornerRadius, 2829 .width = borderConfig->width 2830 }}, 2831 .userData = currentElement->config.userData, 2832 .id = Clay__HashNumber(currentElement->id, currentElement->children.length).id, 2833 .commandType = CLAY_RENDER_COMMAND_TYPE_BORDER, 2834 }; 2835 Clay__AddRenderCommand(renderCommand); 2836 if (borderConfig->width.betweenChildren > 0 && borderConfig->color.a > 0) { 2837 float halfGap = layoutConfig->childGap / 2; 2838 float halfWidth = borderConfig->width.betweenChildren / 2; 2839 Clay_Vector2 borderOffset = { (float)layoutConfig->padding.left - halfGap, (float)layoutConfig->padding.top - halfGap }; 2840 if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { 2841 for (int32_t i = 0; i < currentElement->children.length; ++i) { 2842 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); 2843 if (i > 0) { 2844 Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { 2845 .boundingBox = { currentElementBoundingBox.x + borderOffset.x + scrollOffset.x - halfWidth, currentElementBoundingBox.y + scrollOffset.y, (float)borderConfig->width.betweenChildren, currentElement->dimensions.height }, 2846 .renderData = { .rectangle = { 2847 .backgroundColor = borderConfig->color, 2848 } }, 2849 .userData = currentElement->config.userData, 2850 .id = Clay__HashNumber(currentElement->id, currentElement->children.length + 1 + i).id, 2851 .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, 2852 }); 2853 } 2854 borderOffset.x += (childElement->dimensions.width + (float)layoutConfig->childGap); 2855 } 2856 } else { 2857 for (int32_t i = 0; i < currentElement->children.length; ++i) { 2858 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); 2859 if (i > 0) { 2860 Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { 2861 .boundingBox = { currentElementBoundingBox.x + scrollOffset.x, currentElementBoundingBox.y + borderOffset.y + scrollOffset.y - halfWidth, currentElement->dimensions.width, (float)borderConfig->width.betweenChildren }, 2862 .renderData = { .rectangle = { 2863 .backgroundColor = borderConfig->color, 2864 } }, 2865 .userData = currentElement->config.userData, 2866 .id = Clay__HashNumber(currentElement->id, currentElement->children.length + 1 + i).id, 2867 .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, 2868 }); 2869 } 2870 borderOffset.y += (childElement->dimensions.height + (float)layoutConfig->childGap); 2871 } 2872 } 2873 } 2874 } 2875 if (currentElement->config.overlayColor.a > 0) { 2876 Clay_RenderCommand renderCommand = { 2877 .userData = currentElement->config.userData, 2878 .id = currentElement->id, 2879 .zIndex = root->zIndex, 2880 .commandType = CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_END, 2881 }; 2882 Clay__AddRenderCommand(renderCommand); 2883 } 2884 // This exists because the scissor needs to end _after_ borders between elements 2885 if (closeClipElement) { 2886 Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { 2887 .id = Clay__HashNumber(currentElement->id, rootElement->children.length + 11).id, 2888 .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, 2889 }); 2890 } 2891 } 2892 2893 dfsBuffer.length--; 2894 continue; 2895 } 2896 2897 // This will only be run a single time for each element in downwards DFS order 2898 context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; 2899 Clay_BoundingBox currentElementBoundingBox = { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height }; 2900 Clay__ScrollContainerDataInternal *scrollContainerData = CLAY__NULL; 2901 if (!currentElement->isTextElement) { 2902 if (useStoredBoundingBoxes && currentElement->config.transition.handler) { 2903 bool found = false; 2904 for (int j = 0; j < context->transitionDatas.length; ++j) { 2905 Clay__TransitionDataInternal* transitionData = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, j); 2906 if (transitionData->elementId == currentElement->id) { 2907 found = true; 2908 if (transitionData->state != CLAY_TRANSITION_STATE_IDLE) { 2909 if ((currentElement->config.transition.properties & CLAY_TRANSITION_PROPERTY_X) != 0) currentElementBoundingBox.x = transitionData->currentState.boundingBox.x; 2910 if ((currentElement->config.transition.properties & CLAY_TRANSITION_PROPERTY_Y) != 0) currentElementBoundingBox.y = transitionData->currentState.boundingBox.y; 2911 if ((currentElement->config.transition.properties & CLAY_TRANSITION_PROPERTY_WIDTH) != 0) currentElementBoundingBox.width = transitionData->currentState.boundingBox.width; 2912 if ((currentElement->config.transition.properties & CLAY_TRANSITION_PROPERTY_HEIGHT) != 0) currentElementBoundingBox.height = transitionData->currentState.boundingBox.height; 2913 } 2914 break; 2915 } 2916 } 2917 // An exiting element that completed its transition this frame - skip tree 2918 if (!found && currentElement->config.transition.exit.setFinalState) { 2919 dfsBuffer.length--; 2920 continue; 2921 } 2922 } 2923 if (currentElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { 2924 Clay_FloatingElementConfig *floatingElementConfig = ¤tElement->config.floating; 2925 Clay_Dimensions expand = floatingElementConfig->expand; 2926 currentElementBoundingBox.x -= expand.width; 2927 currentElementBoundingBox.width += expand.width * 2; 2928 currentElementBoundingBox.y -= expand.height; 2929 currentElementBoundingBox.height += expand.height * 2; 2930 } 2931 2932 // Apply scroll offsets to container 2933 if (currentElement->config.clip.horizontal || currentElement->config.clip.vertical) { 2934 // 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 2935 for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { 2936 Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); 2937 if (mapping->layoutElement == currentElement) { 2938 scrollContainerData = mapping; 2939 mapping->boundingBox = currentElementBoundingBox; 2940 scrollOffset = currentElement->config.clip.childOffset; 2941 if (context->externalScrollHandlingEnabled) { 2942 scrollOffset = CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; 2943 } 2944 break; 2945 } 2946 } 2947 } 2948 } 2949 2950 bool offscreen = Clay__ElementIsOffscreen(¤tElementBoundingBox); 2951 2952 // Generate render commands for current element 2953 if (generateRenderCommands && !offscreen) { 2954 if (currentElement->isTextElement) { 2955 Clay_TextElementConfig *textElementConfig = ¤tElement->textConfig; 2956 float naturalLineHeight = currentElement->textElementData.preferredDimensions.height; 2957 float finalLineHeight = textElementConfig->lineHeight > 0 ? (float)textElementConfig->lineHeight : naturalLineHeight; 2958 float lineHeightOffset = (finalLineHeight - naturalLineHeight) / 2; 2959 float yPosition = lineHeightOffset; 2960 for (int32_t lineIndex = 0; lineIndex < currentElement->textElementData.wrappedLines.length; ++lineIndex) { 2961 Clay__WrappedTextLine *wrappedLine = Clay__WrappedTextLineArraySlice_Get(¤tElement->textElementData.wrappedLines, lineIndex); 2962 if (wrappedLine->line.length == 0) { 2963 yPosition += finalLineHeight; 2964 continue; 2965 } 2966 float offset = (currentElementBoundingBox.width - wrappedLine->dimensions.width); 2967 if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_LEFT) { 2968 offset = 0; 2969 } 2970 if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { 2971 offset /= 2; 2972 } 2973 Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { 2974 .boundingBox = { currentElementBoundingBox.x + offset, currentElementBoundingBox.y + yPosition, wrappedLine->dimensions.width, wrappedLine->dimensions.height }, 2975 .renderData = { .text = { 2976 .stringContents = CLAY__INIT(Clay_StringSlice) { .length = wrappedLine->line.length, .chars = wrappedLine->line.chars, .baseChars = currentElement->textElementData.text.chars }, 2977 .textColor = textElementConfig->textColor, 2978 .fontId = textElementConfig->fontId, 2979 .fontSize = textElementConfig->fontSize, 2980 .letterSpacing = textElementConfig->letterSpacing, 2981 .lineHeight = textElementConfig->lineHeight, 2982 }}, 2983 .userData = textElementConfig->userData, 2984 .id = Clay__HashNumber(lineIndex, currentElement->id).id, 2985 .zIndex = root->zIndex, 2986 .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT, 2987 }); 2988 yPosition += finalLineHeight; 2989 2990 if (!context->disableCulling && (currentElementBoundingBox.y + yPosition > context->layoutDimensions.height)) { 2991 break; 2992 } 2993 } 2994 } else { 2995 if (currentElement->config.overlayColor.a > 0) { 2996 Clay_RenderCommand renderCommand = { 2997 .renderData = { 2998 .overlayColor = { .color = currentElement->config.overlayColor } 2999 }, 3000 .userData = currentElement->config.userData, 3001 .id = currentElement->id, 3002 .zIndex = root->zIndex, 3003 .commandType = CLAY_RENDER_COMMAND_TYPE_OVERLAY_COLOR_START, 3004 }; 3005 Clay__AddRenderCommand(renderCommand); 3006 } 3007 if (currentElement->config.image.imageData) { 3008 Clay_RenderCommand renderCommand = { 3009 .boundingBox = currentElementBoundingBox, 3010 .renderData = { 3011 .image = { 3012 .backgroundColor = currentElement->config.backgroundColor, 3013 .cornerRadius = currentElement->config.cornerRadius, 3014 .imageData = currentElement->config.image.imageData, 3015 } 3016 }, 3017 .userData = currentElement->config.userData, 3018 .id = currentElement->id, 3019 .zIndex = root->zIndex, 3020 .commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE, 3021 }; 3022 Clay__AddRenderCommand(renderCommand); 3023 } 3024 if (currentElement->config.custom.customData) { 3025 Clay_RenderCommand renderCommand = { 3026 .boundingBox = currentElementBoundingBox, 3027 .renderData = { 3028 .custom = { 3029 .backgroundColor = currentElement->config.backgroundColor, 3030 .cornerRadius = currentElement->config.cornerRadius, 3031 .customData = currentElement->config.custom.customData, 3032 } 3033 }, 3034 .userData = currentElement->config.userData, 3035 .id = currentElement->id, 3036 .zIndex = root->zIndex, 3037 .commandType = CLAY_RENDER_COMMAND_TYPE_CUSTOM, 3038 }; 3039 Clay__AddRenderCommand(renderCommand); 3040 } 3041 if (currentElement->config.clip.horizontal || currentElement->config.clip.vertical) { 3042 Clay_RenderCommand renderCommand = { 3043 .boundingBox = currentElementBoundingBox, 3044 .renderData = { 3045 .clip = { 3046 .horizontal = currentElement->config.clip.horizontal, 3047 .vertical = currentElement->config.clip.vertical, 3048 } 3049 }, 3050 .userData = currentElement->config.userData, 3051 .id = currentElement->id, 3052 .zIndex = root->zIndex, 3053 .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, 3054 }; 3055 Clay__AddRenderCommand(renderCommand); 3056 } 3057 if (currentElement->config.backgroundColor.a > 0) { 3058 Clay_RenderCommand renderCommand = { 3059 .boundingBox = currentElementBoundingBox, 3060 .renderData = { .rectangle = { 3061 .backgroundColor = currentElement->config.backgroundColor, 3062 .cornerRadius = currentElement->config.cornerRadius, 3063 } }, 3064 .userData = currentElement->config.userData, 3065 .id = currentElement->id, 3066 .zIndex = root->zIndex, 3067 .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, 3068 }; 3069 Clay__AddRenderCommand(renderCommand); 3070 } 3071 } 3072 } 3073 3074 Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(currentElement->id); 3075 hashMapItem->boundingBox = currentElementBoundingBox; 3076 3077 if (currentElement->isTextElement) continue; 3078 3079 // Setup positions for child elements and add to DFS buffer ---------- 3080 3081 // On-axis alignment 3082 Clay_Dimensions contentSizeCurrent = {}; 3083 if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { 3084 for (int32_t i = 0; i < currentElement->children.length; ++i) { 3085 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); 3086 if (childElement->exiting) continue; 3087 contentSizeCurrent.width += childElement->dimensions.width; 3088 contentSizeCurrent.height = CLAY__MAX(contentSizeCurrent.height, childElement->dimensions.height); 3089 } 3090 contentSizeCurrent.width += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap); 3091 float extraSpace = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - contentSizeCurrent.width; 3092 switch (layoutConfig->childAlignment.x) { 3093 case CLAY_ALIGN_X_LEFT: extraSpace = 0; break; 3094 case CLAY_ALIGN_X_CENTER: extraSpace /= 2; break; 3095 default: break; 3096 } 3097 extraSpace = CLAY__MAX(0, extraSpace); 3098 currentElementTreeNode->nextChildOffset.x += extraSpace; 3099 } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { 3100 for (int32_t i = 0; i < currentElement->children.length; ++i) { 3101 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); 3102 if (childElement->exiting) continue; 3103 contentSizeCurrent.width = CLAY__MAX(contentSizeCurrent.width, childElement->dimensions.width); 3104 contentSizeCurrent.height += childElement->dimensions.height; 3105 } 3106 contentSizeCurrent.height += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap); 3107 float extraSpace = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - contentSizeCurrent.height; 3108 switch (layoutConfig->childAlignment.y) { 3109 case CLAY_ALIGN_Y_TOP: extraSpace = 0; break; 3110 case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break; 3111 default: break; 3112 } 3113 extraSpace = CLAY__MAX(0, extraSpace); 3114 currentElementTreeNode->nextChildOffset.y += extraSpace; 3115 } 3116 3117 if (scrollContainerData) { 3118 scrollContainerData->contentSize = CLAY__INIT(Clay_Dimensions) {contentSizeCurrent.width + (float)(layoutConfig->padding.left + layoutConfig->padding.right), contentSizeCurrent.height + (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) }; 3119 } 3120 3121 // Add children to the DFS buffer 3122 dfsBuffer.length += currentElement->children.length; 3123 for (int32_t i = 0; i < currentElement->children.length; ++i) { 3124 Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->children.elements[i]); 3125 Clay_LayoutElementHashMapItem* childMapItem = Clay__GetHashMapItem(childElement->id); 3126 // Alignment along non layout axis 3127 if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { 3128 currentElementTreeNode->nextChildOffset.y = currentElement->config.layout.padding.top; 3129 float whiteSpaceAroundChild = currentElement->dimensions.height - (float)(layoutConfig->padding.top + layoutConfig->padding.bottom) - childElement->dimensions.height; 3130 switch (layoutConfig->childAlignment.y) { 3131 case CLAY_ALIGN_Y_TOP: break; 3132 case CLAY_ALIGN_Y_CENTER: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild / 2; break; 3133 case CLAY_ALIGN_Y_BOTTOM: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild; break; 3134 } 3135 } else { 3136 currentElementTreeNode->nextChildOffset.x = currentElement->config.layout.padding.left; 3137 float whiteSpaceAroundChild = currentElement->dimensions.width - (float)(layoutConfig->padding.left + layoutConfig->padding.right) - childElement->dimensions.width; 3138 switch (layoutConfig->childAlignment.x) { 3139 case CLAY_ALIGN_X_LEFT: break; 3140 case CLAY_ALIGN_X_CENTER: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild / 2; break; 3141 case CLAY_ALIGN_X_RIGHT: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild; break; 3142 } 3143 } 3144 3145 Clay_Vector2 childPosition = { 3146 currentElementBoundingBox.x + currentElementTreeNode->nextChildOffset.x + scrollOffset.x, 3147 currentElementBoundingBox.y + currentElementTreeNode->nextChildOffset.y + scrollOffset.y, 3148 }; 3149 3150 // DFS buffer elements need to be added in reverse because stack traversal happens backwards 3151 uint32_t newNodeIndex = dfsBuffer.length - 1 - i; 3152 dfsBuffer.internalArray[newNodeIndex] = CLAY__INIT(Clay__LayoutElementTreeNode) { 3153 .layoutElement = childElement, 3154 .position = CLAY__INIT(Clay_Vector2) { childPosition.x, childPosition.y }, 3155 .nextChildOffset = { .x = (float)childElement->config.layout.padding.left, .y = (float)childElement->config.layout.padding.top }, 3156 }; 3157 context->treeNodeVisited.internalArray[newNodeIndex] = false; 3158 3159 // Update parent offsets 3160 if (!childElement->exiting) { 3161 if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { 3162 currentElementTreeNode->nextChildOffset.x += childElement->dimensions.width + (float)layoutConfig->childGap; 3163 } else { 3164 currentElementTreeNode->nextChildOffset.y += childElement->dimensions.height + (float)layoutConfig->childGap; 3165 } 3166 } 3167 } 3168 } 3169 3170 if (root->clipElementId) { 3171 Clay_LayoutElementHashMapItem *clipHashMapItem = Clay__GetHashMapItem(root->clipElementId); 3172 if (clipHashMapItem && !Clay__ElementIsOffscreen(&clipHashMapItem->boundingBox)) { 3173 Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .id = Clay__HashNumber(rootElement->id, rootElement->children.length + 11).id, .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END }); 3174 } 3175 } 3176 } 3177 } 3178 3179 CLAY_WASM_EXPORT("Clay_GetPointerOverIds") 3180 CLAY_DLL_EXPORT Clay_ElementIdArray Clay_GetPointerOverIds(void) { 3181 return Clay_GetCurrentContext()->pointerOverIds; 3182 } 3183 3184 #pragma region DebugTools 3185 Clay_Color CLAY__DEBUGVIEW_COLOR_1 = {58, 56, 52, 255}; 3186 Clay_Color CLAY__DEBUGVIEW_COLOR_2 = {62, 60, 58, 255}; 3187 Clay_Color CLAY__DEBUGVIEW_COLOR_3 = {141, 133, 135, 255}; 3188 Clay_Color CLAY__DEBUGVIEW_COLOR_4 = {238, 226, 231, 255}; 3189 Clay_Color CLAY__DEBUGVIEW_COLOR_SELECTED_ROW = {102, 80, 78, 255}; 3190 const int32_t CLAY__DEBUGVIEW_ROW_HEIGHT = 30; 3191 const int32_t CLAY__DEBUGVIEW_OUTER_PADDING = 10; 3192 const int32_t CLAY__DEBUGVIEW_INDENT_WIDTH = 16; 3193 Clay_TextElementConfig Clay__DebugView_TextNameConfig = {.textColor = {238, 226, 231, 255}, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }; 3194 Clay_LayoutConfig Clay__DebugView_ScrollViewItemLayoutConfig = CLAY__DEFAULT_STRUCT; 3195 3196 typedef struct { 3197 Clay_String label; 3198 Clay_Color color; 3199 } Clay__DebugElementConfigTypeLabelConfig; 3200 3201 typedef enum { 3202 CLAY__ELEMENT_CONFIG_TYPE_BACKGROUND_COLOR, 3203 CLAY__ELEMENT_CONFIG_TYPE_OVERLAY_COLOR, 3204 CLAY__ELEMENT_CONFIG_TYPE_CORNER_RADIUS, 3205 CLAY__ELEMENT_CONFIG_TYPE_TEXT, 3206 CLAY__ELEMENT_CONFIG_TYPE_ASPECT, 3207 CLAY__ELEMENT_CONFIG_TYPE_IMAGE, 3208 CLAY__ELEMENT_CONFIG_TYPE_FLOATING, 3209 CLAY__ELEMENT_CONFIG_TYPE_CLIP, 3210 CLAY__ELEMENT_CONFIG_TYPE_BORDER, 3211 CLAY__ELEMENT_CONFIG_TYPE_CUSTOM, 3212 } Clay__DebugElementConfigType; 3213 3214 Clay__DebugElementConfigTypeLabelConfig Clay__DebugGetElementConfigTypeLabel(Clay__DebugElementConfigType type) { 3215 switch (type) { 3216 case CLAY__ELEMENT_CONFIG_TYPE_BACKGROUND_COLOR: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Background"), {243,134,48,255} }; 3217 case CLAY__ELEMENT_CONFIG_TYPE_OVERLAY_COLOR: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Overlay"), { 142,129,206, 255} }; 3218 case CLAY__ELEMENT_CONFIG_TYPE_CORNER_RADIUS: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) {CLAY_STRING("Radius"), {239,148,157, 255 } }; 3219 case CLAY__ELEMENT_CONFIG_TYPE_TEXT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Text"), {105,210,231,255} }; 3220 case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Aspect"), {101,149,194,255} }; 3221 case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Image"), {121,189,154,255} }; 3222 case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Floating"), {250,105,0,255} }; 3223 case CLAY__ELEMENT_CONFIG_TYPE_CLIP: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) {CLAY_STRING("Scroll"), {242, 196, 90, 255} }; 3224 case CLAY__ELEMENT_CONFIG_TYPE_BORDER: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) {CLAY_STRING("Border"), {108, 91, 123, 255} }; 3225 case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Custom"), {11,72,107,255} }; 3226 default: break; 3227 } 3228 return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Error"), {0,0,0,255} }; 3229 } 3230 3231 void Clay__RenderElementConfigTypeLabel(Clay_String label, Clay_Color color, bool offscreen) { 3232 Clay_Color backgroundColor = color; 3233 backgroundColor.a = 90; 3234 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 } } }) { 3235 CLAY_TEXT(label, CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); 3236 } 3237 } 3238 3239 typedef struct { 3240 int32_t rowCount; 3241 int32_t selectedElementRowIndex; 3242 } Clay__RenderDebugLayoutData; 3243 3244 // Returns row count 3245 Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialRootsLength, int32_t highlightedRowIndex) { 3246 Clay_Context* context = Clay_GetCurrentContext(); 3247 Clay__int32_tArray dfsBuffer = context->reusableElementIndexBuffer; 3248 Clay__DebugView_ScrollViewItemLayoutConfig = CLAY__INIT(Clay_LayoutConfig) { .sizing = { .height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT) }, .childGap = 6, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }}; 3249 Clay__RenderDebugLayoutData layoutData = CLAY__DEFAULT_STRUCT; 3250 3251 uint32_t highlightedElementId = 0; 3252 3253 for (int32_t rootIndex = 0; rootIndex < initialRootsLength; ++rootIndex) { 3254 dfsBuffer.length = 0; 3255 Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); 3256 Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); 3257 context->treeNodeVisited.internalArray[0] = false; 3258 if (rootIndex > 0) { 3259 CLAY(CLAY_IDI("Clay__DebugView_EmptyRowOuter", rootIndex), { .layout = { .sizing = {.width = CLAY_SIZING_GROW(0)}, .padding = {CLAY__DEBUGVIEW_INDENT_WIDTH / 2, 0, 0, 0} } }) { 3260 CLAY(CLAY_IDI("Clay__DebugView_EmptyRow", rootIndex), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED((float)CLAY__DEBUGVIEW_ROW_HEIGHT) }}, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .top = 1 } } }) {} 3261 } 3262 layoutData.rowCount++; 3263 } 3264 while (dfsBuffer.length > 0) { 3265 int32_t currentElementIndex = Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1); 3266 Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)currentElementIndex); 3267 if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { 3268 if (!currentElement->isTextElement && currentElement->children.length > 0) { 3269 Clay__CloseElement(); 3270 Clay__CloseElement(); 3271 Clay__CloseElement(); 3272 } 3273 dfsBuffer.length--; 3274 continue; 3275 } 3276 3277 if (currentElement->exiting) { // TODO there is a duplicate ID problem with exiting elements 3278 dfsBuffer.length--; 3279 continue; 3280 } 3281 3282 if (highlightedRowIndex == layoutData.rowCount) { 3283 if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { 3284 context->debugSelectedElementId = currentElement->id; 3285 } 3286 highlightedElementId = currentElement->id; 3287 } 3288 3289 context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; 3290 Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); 3291 bool offscreen = Clay__ElementIsOffscreen(¤tElementData->boundingBox); 3292 if (context->debugSelectedElementId == currentElement->id) { 3293 layoutData.selectedElementRowIndex = layoutData.rowCount; 3294 } 3295 CLAY(CLAY_IDI("Clay__DebugView_ElementOuter", currentElement->id), { .layout = Clay__DebugView_ScrollViewItemLayoutConfig }) { 3296 // Collapse icon / button 3297 if (!(currentElement->isTextElement || currentElement->children.length == 0)) { 3298 CLAY(CLAY_IDI("Clay__DebugView_CollapseElement", currentElement->id), { 3299 .layout = { .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }, 3300 .cornerRadius = CLAY_CORNER_RADIUS(4), 3301 .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = {1, 1, 1, 1, 0} }, 3302 }) { 3303 CLAY_TEXT((currentElementData && currentElementData->debugData->collapsed) ? CLAY_STRING("+") : CLAY_STRING("-"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); 3304 } 3305 } else { // Square dot for empty containers 3306 CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER } } }) { 3307 CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_FIXED(8), CLAY_SIZING_FIXED(8)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3, .cornerRadius = CLAY_CORNER_RADIUS(2) }) {} 3308 } 3309 } 3310 // Collisions and offscreen info 3311 if (currentElementData) { 3312 if (currentElementData->debugData->collision) { 3313 CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 }}, .border = { .color = {177, 147, 8, 255}, .width = {1, 1, 1, 1, 0} } }) { 3314 CLAY_TEXT(CLAY_STRING("Duplicate ID"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 })); 3315 } 3316 } 3317 if (offscreen) { 3318 CLAY_AUTO_ID({ .layout = { .padding = { 8, 8, 2, 2 } }, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { 1, 1, 1, 1, 0} } }) { 3319 CLAY_TEXT(CLAY_STRING("Offscreen"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 })); 3320 } 3321 } 3322 } 3323 if (currentElementData->elementId.stringId.length > 0) { 3324 CLAY_AUTO_ID() { 3325 Clay_TextElementConfig textConfig = offscreen ? CLAY__INIT(Clay_TextElementConfig) { .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 } : Clay__DebugView_TextNameConfig; 3326 CLAY_TEXT(currentElementData->elementId.stringId, textConfig); 3327 if (currentElementData->elementId.offset != 0) { 3328 CLAY_TEXT(CLAY_STRING(" ("), textConfig); 3329 CLAY_TEXT(Clay__IntToString(currentElementData->elementId.offset), textConfig); 3330 CLAY_TEXT(CLAY_STRING(")"), textConfig); 3331 } 3332 } 3333 } 3334 if (currentElement->isTextElement) { 3335 Clay__RenderElementConfigTypeLabel(CLAY_STRING("Text"), CLAY__INIT(Clay_Color) { 105,210,231,255 }, offscreen); 3336 } else { 3337 if (currentElement->config.backgroundColor.a > 0) { 3338 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_BACKGROUND_COLOR); 3339 Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); 3340 } 3341 if (currentElement->config.overlayColor.a > 0) { 3342 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_OVERLAY_COLOR); 3343 Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); 3344 } 3345 if (!Clay__MemCmp((const char*)¤tElement->config.cornerRadius, (const char*)&Clay__CornerRadius_DEFAULT, sizeof(Clay_CornerRadius))) { 3346 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_CORNER_RADIUS); 3347 Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); 3348 } 3349 if (currentElement->config.aspectRatio.aspectRatio != 0) { 3350 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_ASPECT); 3351 Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); 3352 } 3353 if (currentElement->config.image.imageData) { 3354 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_IMAGE); 3355 Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); 3356 } 3357 if (currentElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { 3358 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_FLOATING); 3359 Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); 3360 } 3361 if (currentElement->config.clip.horizontal || currentElement->config.clip.vertical) { 3362 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_CLIP); 3363 Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); 3364 } 3365 if (Clay__BorderHasAnyWidth(¤tElement->config.border)) { 3366 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_BORDER); 3367 Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); 3368 } 3369 if (currentElement->config.custom.customData) { 3370 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_CUSTOM); 3371 Clay__RenderElementConfigTypeLabel(config.label, config.color, offscreen); 3372 } 3373 } 3374 } 3375 3376 // Render the text contents below the element as a non-interactive row 3377 if (currentElement->isTextElement) { 3378 layoutData.rowCount++; 3379 Clay__TextElementData *textElementData = ¤tElement->textElementData; 3380 Clay_TextElementConfig rawTextConfig = offscreen ? CLAY__INIT(Clay_TextElementConfig) { .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 } : Clay__DebugView_TextNameConfig; 3381 CLAY_AUTO_ID({ .layout = { .sizing = { .height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } } }) { 3382 CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_INDENT_WIDTH + 16) } } }) {} 3383 CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); 3384 CLAY_TEXT(textElementData->text.length > 40 ? (CLAY__INIT(Clay_String) { .length = 40, .chars = textElementData->text.chars }) : textElementData->text, rawTextConfig); 3385 if (textElementData->text.length > 40) { 3386 CLAY_TEXT(CLAY_STRING("..."), rawTextConfig); 3387 } 3388 CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); 3389 } 3390 } else if (currentElement->children.length > 0) { 3391 Clay__OpenElement(); 3392 Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .padding = { .left = 8 } } }); 3393 Clay__OpenElement(); 3394 Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .padding = { .left = CLAY__DEBUGVIEW_INDENT_WIDTH }}, .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .left = 1 } }}); 3395 Clay__OpenElement(); 3396 Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM } }); 3397 } 3398 3399 layoutData.rowCount++; 3400 if (!(currentElement->isTextElement || (currentElementData && currentElementData->debugData->collapsed))) { 3401 for (int32_t i = currentElement->children.length - 1; i >= 0; --i) { 3402 Clay__int32_tArray_Add(&dfsBuffer, currentElement->children.elements[i]); 3403 context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked 3404 } 3405 } 3406 } 3407 } 3408 3409 if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { 3410 Clay_ElementId collapseButtonId = Clay__HashString(CLAY_STRING("Clay__DebugView_CollapseElement"), 0); 3411 for (int32_t i = (int)context->pointerOverIds.length - 1; i >= 0; i--) { 3412 Clay_ElementId *elementId = Clay_ElementIdArray_Get(&context->pointerOverIds, i); 3413 if (elementId->baseId == collapseButtonId.baseId) { 3414 Clay_LayoutElementHashMapItem *highlightedItem = Clay__GetHashMapItem(elementId->offset); 3415 highlightedItem->debugData->collapsed = !highlightedItem->debugData->collapsed; 3416 break; 3417 } 3418 } 3419 } 3420 3421 if (highlightedElementId) { 3422 CLAY(CLAY_ID("Clay__DebugView_ElementHighlight"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .floating = { .parentId = highlightedElementId, .zIndex = 32767, .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID } }) { 3423 CLAY(CLAY_ID("Clay__DebugView_ElementHighlightRectangle"), { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .backgroundColor = Clay__debugViewHighlightColor }) {} 3424 } 3425 } 3426 return layoutData; 3427 } 3428 3429 void Clay__RenderDebugLayoutSizing(Clay_SizingAxis sizing, Clay_TextElementConfig infoTextConfig) { 3430 Clay_String sizingLabel = CLAY_STRING("GROW"); 3431 if (sizing.type == CLAY__SIZING_TYPE_FIT) { 3432 sizingLabel = CLAY_STRING("FIT"); 3433 } else if (sizing.type == CLAY__SIZING_TYPE_PERCENT) { 3434 sizingLabel = CLAY_STRING("PERCENT"); 3435 } else if (sizing.type == CLAY__SIZING_TYPE_FIXED) { 3436 sizingLabel = CLAY_STRING("FIXED"); 3437 } 3438 CLAY_TEXT(sizingLabel, infoTextConfig); 3439 if (sizing.type == CLAY__SIZING_TYPE_GROW || sizing.type == CLAY__SIZING_TYPE_FIT || sizing.type == CLAY__SIZING_TYPE_FIXED) { 3440 CLAY_TEXT(CLAY_STRING("("), infoTextConfig); 3441 if (sizing.size.minMax.min != 0) { 3442 CLAY_TEXT(CLAY_STRING("min: "), infoTextConfig); 3443 CLAY_TEXT(Clay__IntToString(sizing.size.minMax.min), infoTextConfig); 3444 if (sizing.size.minMax.max != CLAY__MAXFLOAT) { 3445 CLAY_TEXT(CLAY_STRING(", "), infoTextConfig); 3446 } 3447 } 3448 if (sizing.size.minMax.max != CLAY__MAXFLOAT) { 3449 CLAY_TEXT(CLAY_STRING("max: "), infoTextConfig); 3450 CLAY_TEXT(Clay__IntToString(sizing.size.minMax.max), infoTextConfig); 3451 } 3452 CLAY_TEXT(CLAY_STRING(")"), infoTextConfig); 3453 } else if (sizing.type == CLAY__SIZING_TYPE_PERCENT) { 3454 CLAY_TEXT(CLAY_STRING("("), infoTextConfig); 3455 CLAY_TEXT(Clay__IntToString(sizing.size.percent * 100), infoTextConfig); 3456 CLAY_TEXT(CLAY_STRING("%)"), infoTextConfig); 3457 } 3458 } 3459 3460 void Clay__DebugViewRenderElementConfigHeader(Clay_String elementId, Clay__DebugElementConfigType type) { 3461 Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(type); 3462 Clay_Color backgroundColor = config.color; 3463 backgroundColor.a = 90; 3464 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 } } }) { 3465 CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); 3466 } 3467 } 3468 3469 void Clay__RenderDebugViewColor(Clay_Color color, Clay_TextElementConfig textConfig) { 3470 CLAY_AUTO_ID({ .layout = { .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { 3471 CLAY_TEXT(CLAY_STRING("{ r: "), textConfig); 3472 CLAY_TEXT(Clay__IntToString(color.r), textConfig); 3473 CLAY_TEXT(CLAY_STRING(", g: "), textConfig); 3474 CLAY_TEXT(Clay__IntToString(color.g), textConfig); 3475 CLAY_TEXT(CLAY_STRING(", b: "), textConfig); 3476 CLAY_TEXT(Clay__IntToString(color.b), textConfig); 3477 CLAY_TEXT(CLAY_STRING(", a: "), textConfig); 3478 CLAY_TEXT(Clay__IntToString(color.a), textConfig); 3479 CLAY_TEXT(CLAY_STRING(" }"), textConfig); 3480 CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_FIXED(10) } } }) {} 3481 CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8)} }, .backgroundColor = color, .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = CLAY__DEBUGVIEW_COLOR_4, .width = { 1, 1, 1, 1, 0 } } }) {} 3482 } 3483 } 3484 3485 void Clay__RenderDebugViewCornerRadius(Clay_CornerRadius cornerRadius, Clay_TextElementConfig textConfig) { 3486 CLAY_AUTO_ID({ .layout = { .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { 3487 CLAY_TEXT(CLAY_STRING("{ topLeft: "), textConfig); 3488 CLAY_TEXT(Clay__IntToString(cornerRadius.topLeft), textConfig); 3489 CLAY_TEXT(CLAY_STRING(", topRight: "), textConfig); 3490 CLAY_TEXT(Clay__IntToString(cornerRadius.topRight), textConfig); 3491 CLAY_TEXT(CLAY_STRING(", bottomLeft: "), textConfig); 3492 CLAY_TEXT(Clay__IntToString(cornerRadius.bottomLeft), textConfig); 3493 CLAY_TEXT(CLAY_STRING(", bottomRight: "), textConfig); 3494 CLAY_TEXT(Clay__IntToString(cornerRadius.bottomRight), textConfig); 3495 CLAY_TEXT(CLAY_STRING(" }"), textConfig); 3496 } 3497 } 3498 3499 void HandleDebugViewCloseButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData) { 3500 Clay_Context* context = Clay_GetCurrentContext(); 3501 (void) elementId; (void) pointerInfo; (void) userData; 3502 if (pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { 3503 context->debugModeEnabled = false; 3504 } 3505 } 3506 3507 void Clay__RenderDebugView(void) { 3508 Clay_Context* context = Clay_GetCurrentContext(); 3509 Clay_ElementId closeButtonId = Clay__HashString(CLAY_STRING("Clay__DebugViewTopHeaderCloseButtonOuter"), 0); 3510 if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { 3511 for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { 3512 Clay_ElementId *elementId = Clay_ElementIdArray_Get(&context->pointerOverIds, i); 3513 if (elementId->id == closeButtonId.id) { 3514 context->debugModeEnabled = false; 3515 return; 3516 } 3517 } 3518 } 3519 3520 uint32_t initialRootsLength = context->layoutElementTreeRoots.length; 3521 uint32_t initialElementsLength = context->layoutElements.length; 3522 Clay_TextElementConfig infoTextConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); 3523 Clay_TextElementConfig infoTitleConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); 3524 Clay_ElementId scrollId = Clay__HashString(CLAY_STRING("Clay__DebugViewOuterScrollPane"), 0); 3525 float scrollYOffset = 0; 3526 bool pointerInDebugView = context->pointerInfo.position.y < context->layoutDimensions.height - 300; 3527 for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) { 3528 Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); 3529 if (scrollContainerData->elementId == scrollId.id) { 3530 if (!context->externalScrollHandlingEnabled) { 3531 scrollYOffset = scrollContainerData->scrollPosition.y; 3532 } else { 3533 pointerInDebugView = context->pointerInfo.position.y + scrollContainerData->scrollPosition.y < context->layoutDimensions.height - 300; 3534 } 3535 break; 3536 } 3537 } 3538 int32_t highlightedRow = pointerInDebugView 3539 ? (int32_t)((context->pointerInfo.position.y - scrollYOffset) / (float)CLAY__DEBUGVIEW_ROW_HEIGHT) - 1 3540 : -1; 3541 if (context->pointerInfo.position.x < context->layoutDimensions.width - (float)Clay__debugViewWidth) { 3542 highlightedRow = -1; 3543 } 3544 Clay__RenderDebugLayoutData layoutData = CLAY__DEFAULT_STRUCT; 3545 CLAY(CLAY_ID("Clay__DebugView"), { 3546 .layout = { .sizing = { CLAY_SIZING_FIXED((float)Clay__debugViewWidth) , CLAY_SIZING_FIXED(context->layoutDimensions.height) }, .layoutDirection = CLAY_TOP_TO_BOTTOM }, 3547 .floating = { .zIndex = 32765, .attachPoints = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_CENTER }, .attachTo = CLAY_ATTACH_TO_ROOT, .clipTo = CLAY_CLIP_TO_ATTACHED_PARENT }, 3548 .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .bottom = 1 } } 3549 }) { 3550 CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_2 }) { 3551 CLAY_TEXT(CLAY_STRING("Clay Debug Tools"), infoTextConfig); 3552 CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} 3553 // Close button 3554 CLAY_AUTO_ID({ 3555 .layout = { .sizing = {CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 10), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 10)}, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }, 3556 .backgroundColor = {217,91,67,80}, 3557 .cornerRadius = CLAY_CORNER_RADIUS(4), 3558 .border = { .color = { 217,91,67,255 }, .width = { 1, 1, 1, 1, 0 } }, 3559 }) { 3560 Clay_OnHover(HandleDebugViewCloseButtonInteraction, 0); 3561 CLAY_TEXT(CLAY_STRING("x"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); 3562 } 3563 } 3564 CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(1)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3 } ) {} 3565 CLAY(scrollId, { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .clip = { .horizontal = true, .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { 3566 CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = ((initialElementsLength + initialRootsLength) & 1) == 0 ? CLAY__DEBUGVIEW_COLOR_2 : CLAY__DEBUGVIEW_COLOR_1 }) { 3567 Clay_ElementId panelContentsId = Clay__HashString(CLAY_STRING("Clay__DebugViewPaneOuter"), 0); 3568 // Element list 3569 CLAY(panelContentsId, { .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .floating = { .zIndex = 32766, .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, .attachTo = CLAY_ATTACH_TO_PARENT, .clipTo = CLAY_CLIP_TO_ATTACHED_PARENT } }) { 3570 CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = { CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { 3571 layoutData = Clay__RenderDebugLayoutElementsList((int32_t)initialRootsLength, highlightedRow); 3572 } 3573 } 3574 float contentWidth = Clay__GetHashMapItem(panelContentsId.id)->layoutElement->dimensions.width; 3575 CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(contentWidth) }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {} 3576 for (int32_t i = 0; i < layoutData.rowCount; i++) { 3577 Clay_Color rowColor = (i & 1) == 0 ? CLAY__DEBUGVIEW_COLOR_2 : CLAY__DEBUGVIEW_COLOR_1; 3578 if (i == layoutData.selectedElementRowIndex) { 3579 rowColor = CLAY__DEBUGVIEW_COLOR_SELECTED_ROW; 3580 } 3581 if (i == highlightedRow) { 3582 rowColor.r *= 1.25f; 3583 rowColor.g *= 1.25f; 3584 rowColor.b *= 1.25f; 3585 } 3586 CLAY_AUTO_ID({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = rowColor } ) {} 3587 } 3588 } 3589 } 3590 CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(1)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3 }) {} 3591 if (context->debugSelectedElementId != 0) { 3592 Clay_LayoutElementHashMapItem *selectedItem = Clay__GetHashMapItem(context->debugSelectedElementId); 3593 CLAY_AUTO_ID({ 3594 .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(300)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, 3595 .backgroundColor = CLAY__DEBUGVIEW_COLOR_2 , 3596 .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, 3597 .border = { .color = CLAY__DEBUGVIEW_COLOR_3, .width = { .betweenChildren = 1 } } 3598 }) { 3599 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} } }) { 3600 CLAY_TEXT(CLAY_STRING("Element Configuration"), infoTextConfig); 3601 CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} 3602 if (selectedItem->elementId.stringId.length != 0) { 3603 CLAY_TEXT(selectedItem->elementId.stringId, infoTitleConfig); 3604 if (selectedItem->elementId.offset != 0) { 3605 CLAY_TEXT(CLAY_STRING(" ("), infoTitleConfig); 3606 CLAY_TEXT(Clay__IntToString(selectedItem->elementId.offset), infoTitleConfig); 3607 CLAY_TEXT(CLAY_STRING(")"), infoTitleConfig); 3608 } 3609 } 3610 } 3611 Clay_Padding attributeConfigPadding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 8, 8}; 3612 // Clay_LayoutConfig debug info 3613 CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { 3614 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 } } }) { 3615 CLAY_TEXT(CLAY_STRING("Layout"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); 3616 } 3617 // .boundingBox 3618 CLAY_TEXT(CLAY_STRING("Bounding Box"), infoTitleConfig); 3619 CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { 3620 CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); 3621 CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.x), infoTextConfig); 3622 CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); 3623 CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.y), infoTextConfig); 3624 CLAY_TEXT(CLAY_STRING(", width: "), infoTextConfig); 3625 CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.width), infoTextConfig); 3626 CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); 3627 CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.height), infoTextConfig); 3628 CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); 3629 } 3630 if (!selectedItem->layoutElement->isTextElement) { 3631 // .layoutDirection 3632 CLAY_TEXT(CLAY_STRING("Layout Direction"), infoTitleConfig); 3633 Clay_LayoutConfig *layoutConfig = &selectedItem->layoutElement->config.layout; 3634 CLAY_TEXT(layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM ? CLAY_STRING("TOP_TO_BOTTOM") : CLAY_STRING("LEFT_TO_RIGHT"), infoTextConfig); 3635 // .sizing 3636 CLAY_TEXT(CLAY_STRING("Sizing"), infoTitleConfig); 3637 CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { 3638 CLAY_TEXT(CLAY_STRING("width: "), infoTextConfig); 3639 Clay__RenderDebugLayoutSizing(layoutConfig->sizing.width, infoTextConfig); 3640 } 3641 CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { 3642 CLAY_TEXT(CLAY_STRING("height: "), infoTextConfig); 3643 Clay__RenderDebugLayoutSizing(layoutConfig->sizing.height, infoTextConfig); 3644 } 3645 // .padding 3646 CLAY_TEXT(CLAY_STRING("Padding"), infoTitleConfig); 3647 CLAY(CLAY_ID("Clay__DebugViewElementInfoPadding"), { }) { 3648 CLAY_TEXT(CLAY_STRING("{ left: "), infoTextConfig); 3649 CLAY_TEXT(Clay__IntToString(layoutConfig->padding.left), infoTextConfig); 3650 CLAY_TEXT(CLAY_STRING(", right: "), infoTextConfig); 3651 CLAY_TEXT(Clay__IntToString(layoutConfig->padding.right), infoTextConfig); 3652 CLAY_TEXT(CLAY_STRING(", top: "), infoTextConfig); 3653 CLAY_TEXT(Clay__IntToString(layoutConfig->padding.top), infoTextConfig); 3654 CLAY_TEXT(CLAY_STRING(", bottom: "), infoTextConfig); 3655 CLAY_TEXT(Clay__IntToString(layoutConfig->padding.bottom), infoTextConfig); 3656 CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); 3657 } 3658 // .childGap 3659 CLAY_TEXT(CLAY_STRING("Child Gap"), infoTitleConfig); 3660 CLAY_TEXT(Clay__IntToString(layoutConfig->childGap), infoTextConfig); 3661 // .childAlignment 3662 CLAY_TEXT(CLAY_STRING("Child Alignment"), infoTitleConfig); 3663 CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { 3664 CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); 3665 Clay_String alignX = CLAY_STRING("LEFT"); 3666 if (layoutConfig->childAlignment.x == CLAY_ALIGN_X_CENTER) { 3667 alignX = CLAY_STRING("CENTER"); 3668 } else if (layoutConfig->childAlignment.x == CLAY_ALIGN_X_RIGHT) { 3669 alignX = CLAY_STRING("RIGHT"); 3670 } 3671 CLAY_TEXT(alignX, infoTextConfig); 3672 CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); 3673 Clay_String alignY = CLAY_STRING("TOP"); 3674 if (layoutConfig->childAlignment.y == CLAY_ALIGN_Y_CENTER) { 3675 alignY = CLAY_STRING("CENTER"); 3676 } else if (layoutConfig->childAlignment.y == CLAY_ALIGN_Y_BOTTOM) { 3677 alignY = CLAY_STRING("BOTTOM"); 3678 } 3679 CLAY_TEXT(alignY, infoTextConfig); 3680 CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); 3681 } 3682 } 3683 } 3684 if (selectedItem->layoutElement->isTextElement) { 3685 Clay_TextElementConfig *textConfig = &selectedItem->layoutElement->textConfig; 3686 CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { 3687 Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_TEXT); 3688 // .fontSize 3689 CLAY_TEXT(CLAY_STRING("Font Size"), infoTitleConfig); 3690 CLAY_TEXT(Clay__IntToString(textConfig->fontSize), infoTextConfig); 3691 // .fontId 3692 CLAY_TEXT(CLAY_STRING("Font ID"), infoTitleConfig); 3693 CLAY_TEXT(Clay__IntToString(textConfig->fontId), infoTextConfig); 3694 // .lineHeight 3695 CLAY_TEXT(CLAY_STRING("Line Height"), infoTitleConfig); 3696 CLAY_TEXT(textConfig->lineHeight == 0 ? CLAY_STRING("auto") : Clay__IntToString(textConfig->lineHeight), infoTextConfig); 3697 // .letterSpacing 3698 CLAY_TEXT(CLAY_STRING("Letter Spacing"), infoTitleConfig); 3699 CLAY_TEXT(Clay__IntToString(textConfig->letterSpacing), infoTextConfig); 3700 // .wrapMode 3701 CLAY_TEXT(CLAY_STRING("Wrap Mode"), infoTitleConfig); 3702 Clay_String wrapMode = CLAY_STRING("WORDS"); 3703 if (textConfig->wrapMode == CLAY_TEXT_WRAP_NONE) { 3704 wrapMode = CLAY_STRING("NONE"); 3705 } else if (textConfig->wrapMode == CLAY_TEXT_WRAP_NEWLINES) { 3706 wrapMode = CLAY_STRING("NEWLINES"); 3707 } 3708 CLAY_TEXT(wrapMode, infoTextConfig); 3709 // .textAlignment 3710 CLAY_TEXT(CLAY_STRING("Text Alignment"), infoTitleConfig); 3711 Clay_String textAlignment = CLAY_STRING("LEFT"); 3712 if (textConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) { 3713 textAlignment = CLAY_STRING("CENTER"); 3714 } else if (textConfig->textAlignment == CLAY_TEXT_ALIGN_RIGHT) { 3715 textAlignment = CLAY_STRING("RIGHT"); 3716 } 3717 CLAY_TEXT(textAlignment, infoTextConfig); 3718 // .textColor 3719 CLAY_TEXT(CLAY_STRING("Text Color"), infoTitleConfig); 3720 Clay__RenderDebugViewColor(textConfig->textColor, infoTextConfig); 3721 } 3722 } else { 3723 CLAY(CLAY_ID("Clay__DebugViewElementInfoSharedBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { 3724 Clay__DebugElementConfigTypeLabelConfig labelConfig = Clay__DebugGetElementConfigTypeLabel(CLAY__ELEMENT_CONFIG_TYPE_BACKGROUND_COLOR); 3725 Clay_Color backgroundColor = labelConfig.color; 3726 backgroundColor.a = 90; 3727 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 } } }) { 3728 CLAY_TEXT(CLAY_STRING("Color & Radius"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); 3729 } 3730 // .backgroundColor 3731 if (selectedItem->layoutElement->config.backgroundColor.a > 0) { 3732 CLAY_TEXT(CLAY_STRING("Background Color"), infoTitleConfig); 3733 Clay__RenderDebugViewColor(selectedItem->layoutElement->config.backgroundColor, infoTextConfig); 3734 } 3735 // .cornerRadius 3736 if (!Clay__MemCmp((const char*)&selectedItem->layoutElement->config.cornerRadius, (const char*)&Clay__CornerRadius_DEFAULT, sizeof(Clay_CornerRadius))) { 3737 CLAY_TEXT(CLAY_STRING("Corner Radius"), infoTitleConfig); 3738 Clay__RenderDebugViewCornerRadius(selectedItem->layoutElement->config.cornerRadius, infoTextConfig); 3739 } 3740 // .overlayColor 3741 if (selectedItem->layoutElement->config.overlayColor.a > 0) { 3742 CLAY_TEXT(CLAY_STRING("Overlay Color"), infoTitleConfig); 3743 Clay__RenderDebugViewColor(selectedItem->layoutElement->config.overlayColor, infoTextConfig); 3744 } 3745 } 3746 if (selectedItem->layoutElement->config.aspectRatio.aspectRatio > 0) { 3747 Clay_AspectRatioElementConfig *aspectRatioConfig = &selectedItem->layoutElement->config.aspectRatio; 3748 CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatioBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { 3749 Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_ASPECT); 3750 CLAY_TEXT(CLAY_STRING("Aspect Ratio"), infoTitleConfig); 3751 // Aspect Ratio 3752 CLAY(CLAY_ID("Clay__DebugViewElementInfoAspectRatio"), { }) { 3753 CLAY_TEXT(Clay__IntToString(aspectRatioConfig->aspectRatio), infoTextConfig); 3754 CLAY_TEXT(CLAY_STRING("."), infoTextConfig); 3755 float frac = aspectRatioConfig->aspectRatio - (int)(aspectRatioConfig->aspectRatio); 3756 frac *= 100; 3757 if ((int)frac < 10) { 3758 CLAY_TEXT(CLAY_STRING("0"), infoTextConfig); 3759 } 3760 CLAY_TEXT(Clay__IntToString(frac), infoTextConfig); 3761 } 3762 } 3763 } 3764 if (selectedItem->layoutElement->config.image.imageData) { 3765 Clay_ImageElementConfig *imageConfig = &selectedItem->layoutElement->config.image; 3766 Clay_AspectRatioElementConfig aspectConfig = { 1 }; 3767 if (selectedItem->layoutElement->config.aspectRatio.aspectRatio > 0) { 3768 aspectConfig = selectedItem->layoutElement->config.aspectRatio; 3769 } 3770 CLAY(CLAY_ID("Clay__DebugViewElementInfoImageBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { 3771 Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_IMAGE); 3772 // Image Preview 3773 CLAY_TEXT(CLAY_STRING("Preview"), infoTitleConfig); 3774 CLAY_AUTO_ID({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(64, 128), .height = CLAY_SIZING_GROW(64, 128) }}, .aspectRatio = aspectConfig, .image = *imageConfig }) {} 3775 } 3776 } 3777 if (selectedItem->layoutElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE) { 3778 Clay_FloatingElementConfig* floatingConfig = &selectedItem->layoutElement->config.floating; 3779 CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { 3780 Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_FLOATING); 3781 // .offset 3782 CLAY_TEXT(CLAY_STRING("Offset"), infoTitleConfig); 3783 CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { 3784 CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); 3785 CLAY_TEXT(Clay__IntToString(floatingConfig->offset.x), infoTextConfig); 3786 CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); 3787 CLAY_TEXT(Clay__IntToString(floatingConfig->offset.y), infoTextConfig); 3788 CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); 3789 } 3790 // .expand 3791 CLAY_TEXT(CLAY_STRING("Expand"), infoTitleConfig); 3792 CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { 3793 CLAY_TEXT(CLAY_STRING("{ width: "), infoTextConfig); 3794 CLAY_TEXT(Clay__IntToString(floatingConfig->expand.width), infoTextConfig); 3795 CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); 3796 CLAY_TEXT(Clay__IntToString(floatingConfig->expand.height), infoTextConfig); 3797 CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); 3798 } 3799 // .zIndex 3800 CLAY_TEXT(CLAY_STRING("z-index"), infoTitleConfig); 3801 CLAY_TEXT(Clay__IntToString(floatingConfig->zIndex), infoTextConfig); 3802 // .parentId 3803 CLAY_TEXT(CLAY_STRING("Parent"), infoTitleConfig); 3804 Clay_LayoutElementHashMapItem *hashItem = Clay__GetHashMapItem(floatingConfig->parentId); 3805 CLAY_TEXT(hashItem->elementId.stringId, infoTextConfig); 3806 // .attachPoints 3807 CLAY_TEXT(CLAY_STRING("Attach Points"), infoTitleConfig); 3808 CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { 3809 CLAY_TEXT(CLAY_STRING("{ element: "), infoTextConfig); 3810 Clay_String attachPointElement = CLAY_STRING("LEFT_TOP"); 3811 if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_CENTER) { 3812 attachPointElement = CLAY_STRING("LEFT_CENTER"); 3813 } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_LEFT_BOTTOM) { 3814 attachPointElement = CLAY_STRING("LEFT_BOTTOM"); 3815 } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_TOP) { 3816 attachPointElement = CLAY_STRING("CENTER_TOP"); 3817 } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_CENTER) { 3818 attachPointElement = CLAY_STRING("CENTER_CENTER"); 3819 } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_CENTER_BOTTOM) { 3820 attachPointElement = CLAY_STRING("CENTER_BOTTOM"); 3821 } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_TOP) { 3822 attachPointElement = CLAY_STRING("RIGHT_TOP"); 3823 } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_CENTER) { 3824 attachPointElement = CLAY_STRING("RIGHT_CENTER"); 3825 } else if (floatingConfig->attachPoints.element == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { 3826 attachPointElement = CLAY_STRING("RIGHT_BOTTOM"); 3827 } 3828 CLAY_TEXT(attachPointElement, infoTextConfig); 3829 Clay_String attachPointParent = CLAY_STRING("LEFT_TOP"); 3830 if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_CENTER) { 3831 attachPointParent = CLAY_STRING("LEFT_CENTER"); 3832 } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_LEFT_BOTTOM) { 3833 attachPointParent = CLAY_STRING("LEFT_BOTTOM"); 3834 } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_TOP) { 3835 attachPointParent = CLAY_STRING("CENTER_TOP"); 3836 } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_CENTER) { 3837 attachPointParent = CLAY_STRING("CENTER_CENTER"); 3838 } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_CENTER_BOTTOM) { 3839 attachPointParent = CLAY_STRING("CENTER_BOTTOM"); 3840 } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_TOP) { 3841 attachPointParent = CLAY_STRING("RIGHT_TOP"); 3842 } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_CENTER) { 3843 attachPointParent = CLAY_STRING("RIGHT_CENTER"); 3844 } else if (floatingConfig->attachPoints.parent == CLAY_ATTACH_POINT_RIGHT_BOTTOM) { 3845 attachPointParent = CLAY_STRING("RIGHT_BOTTOM"); 3846 } 3847 CLAY_TEXT(CLAY_STRING(", parent: "), infoTextConfig); 3848 CLAY_TEXT(attachPointParent, infoTextConfig); 3849 CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); 3850 } 3851 // .pointerCaptureMode 3852 CLAY_TEXT(CLAY_STRING("Pointer Capture Mode"), infoTitleConfig); 3853 Clay_String pointerCaptureMode = CLAY_STRING("NONE"); 3854 if (floatingConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH) { 3855 pointerCaptureMode = CLAY_STRING("PASSTHROUGH"); 3856 } 3857 CLAY_TEXT(pointerCaptureMode, infoTextConfig); 3858 // .attachTo 3859 CLAY_TEXT(CLAY_STRING("Attach To"), infoTitleConfig); 3860 Clay_String attachTo = CLAY_STRING("NONE"); 3861 if (floatingConfig->attachTo == CLAY_ATTACH_TO_PARENT) { 3862 attachTo = CLAY_STRING("PARENT"); 3863 } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) { 3864 attachTo = CLAY_STRING("ELEMENT_WITH_ID"); 3865 } else if (floatingConfig->attachTo == CLAY_ATTACH_TO_ROOT) { 3866 attachTo = CLAY_STRING("ROOT"); 3867 } 3868 CLAY_TEXT(attachTo, infoTextConfig); 3869 // .clipTo 3870 CLAY_TEXT(CLAY_STRING("Clip To"), infoTitleConfig); 3871 Clay_String clipTo = CLAY_STRING("ATTACHED_PARENT"); 3872 if (floatingConfig->clipTo == CLAY_CLIP_TO_NONE) { 3873 clipTo = CLAY_STRING("NONE"); 3874 } 3875 CLAY_TEXT(clipTo, infoTextConfig); 3876 } 3877 } 3878 Clay_ClipElementConfig *clipConfig = &selectedItem->layoutElement->config.clip; 3879 if (clipConfig->horizontal || clipConfig->vertical) { 3880 CLAY_AUTO_ID({ .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { 3881 Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_CLIP); 3882 // .vertical 3883 CLAY_TEXT(CLAY_STRING("Vertical"), infoTitleConfig); 3884 CLAY_TEXT(clipConfig->vertical ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); 3885 // .horizontal 3886 CLAY_TEXT(CLAY_STRING("Horizontal"), infoTitleConfig); 3887 CLAY_TEXT(clipConfig->horizontal ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); 3888 } 3889 } 3890 Clay_BorderElementConfig *borderConfig = &selectedItem->layoutElement->config.border; 3891 if (Clay__BorderHasAnyWidth(borderConfig)) { 3892 CLAY(CLAY_ID("Clay__DebugViewElementInfoBorderBody"), { .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { 3893 Clay__DebugViewRenderElementConfigHeader(selectedItem->elementId.stringId, CLAY__ELEMENT_CONFIG_TYPE_BORDER); 3894 CLAY_TEXT(CLAY_STRING("Border Widths"), infoTitleConfig); 3895 CLAY_AUTO_ID({ .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { 3896 CLAY_TEXT(CLAY_STRING("{ left: "), infoTextConfig); 3897 CLAY_TEXT(Clay__IntToString(borderConfig->width.left), infoTextConfig); 3898 CLAY_TEXT(CLAY_STRING(", right: "), infoTextConfig); 3899 CLAY_TEXT(Clay__IntToString(borderConfig->width.right), infoTextConfig); 3900 CLAY_TEXT(CLAY_STRING(", top: "), infoTextConfig); 3901 CLAY_TEXT(Clay__IntToString(borderConfig->width.top), infoTextConfig); 3902 CLAY_TEXT(CLAY_STRING(", bottom: "), infoTextConfig); 3903 CLAY_TEXT(Clay__IntToString(borderConfig->width.bottom), infoTextConfig); 3904 CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); 3905 } 3906 // .textColor 3907 CLAY_TEXT(CLAY_STRING("Border Color"), infoTitleConfig); 3908 Clay__RenderDebugViewColor(borderConfig->color, infoTextConfig); 3909 } 3910 } 3911 } 3912 } 3913 } else { 3914 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() } }) { 3915 Clay_TextElementConfig warningConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); 3916 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} } }) { 3917 CLAY_TEXT(CLAY_STRING("Warnings"), warningConfig); 3918 } 3919 CLAY(CLAY_ID("Clay__DebugViewWarningsTopBorder"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(1)} }, .backgroundColor = {200, 200, 200, 255} }) {} 3920 int32_t previousWarningsLength = context->warnings.length; 3921 for (int32_t i = 0; i < previousWarningsLength; i++) { 3922 Clay__Warning warning = context->warnings.internalArray[i]; 3923 CLAY(CLAY_IDI("Clay__DebugViewWarningItem", i), { .layout = { .sizing = {.height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .childGap = 8, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} } }) { 3924 CLAY_TEXT(warning.baseMessage, warningConfig); 3925 if (warning.dynamicMessage.length > 0) { 3926 CLAY_TEXT(warning.dynamicMessage, warningConfig); 3927 } 3928 } 3929 } 3930 } 3931 } 3932 } 3933 } 3934 #pragma endregion 3935 3936 uint32_t Clay__debugViewWidth = 400; 3937 Clay_Color Clay__debugViewHighlightColor = { 168, 66, 28, 100 }; 3938 3939 Clay__WarningArray Clay__WarningArray_Allocate_Arena(int32_t capacity, Clay_Arena *arena) { 3940 size_t totalSizeBytes = capacity * sizeof(Clay_String); 3941 Clay__WarningArray array = {.capacity = capacity, .length = 0}; 3942 uintptr_t nextAllocOffset = arena->nextAllocation + (64 - (arena->nextAllocation % 64)); 3943 if (nextAllocOffset + totalSizeBytes <= arena->capacity) { 3944 array.internalArray = (Clay__Warning*)((uintptr_t)arena->memory + (uintptr_t)nextAllocOffset); 3945 arena->nextAllocation = nextAllocOffset + totalSizeBytes; 3946 } 3947 else { 3948 Clay__currentContext->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 3949 .errorType = CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, 3950 .errorText = CLAY_STRING("Clay attempted to allocate memory in its arena, but ran out of capacity. Try increasing the capacity of the arena passed to Clay_Initialize()"), 3951 .userData = Clay__currentContext->errorHandler.userData }); 3952 } 3953 return array; 3954 } 3955 3956 Clay__Warning *Clay__WarningArray_Add(Clay__WarningArray *array, Clay__Warning item) 3957 { 3958 if (array->length < array->capacity) { 3959 array->internalArray[array->length++] = item; 3960 return &array->internalArray[array->length - 1]; 3961 } 3962 return &CLAY__WARNING_DEFAULT; 3963 } 3964 3965 void* Clay__Array_Allocate_Arena(int32_t capacity, uint32_t itemSize, Clay_Arena *arena) 3966 { 3967 size_t totalSizeBytes = capacity * itemSize; 3968 uintptr_t nextAllocOffset = arena->nextAllocation + ((64 - (arena->nextAllocation % 64)) & 63); 3969 if (nextAllocOffset + totalSizeBytes <= arena->capacity) { 3970 arena->nextAllocation = nextAllocOffset + totalSizeBytes; 3971 return (void*)((uintptr_t)arena->memory + (uintptr_t)nextAllocOffset); 3972 } 3973 else { 3974 Clay__currentContext->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 3975 .errorType = CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, 3976 .errorText = CLAY_STRING("Clay attempted to allocate memory in its arena, but ran out of capacity. Try increasing the capacity of the arena passed to Clay_Initialize()"), 3977 .userData = Clay__currentContext->errorHandler.userData }); 3978 } 3979 return CLAY__NULL; 3980 } 3981 3982 bool Clay__Array_RangeCheck(int32_t index, int32_t length) 3983 { 3984 if (index < length && index >= 0) { 3985 return true; 3986 } 3987 Clay_Context* context = Clay_GetCurrentContext(); 3988 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 3989 .errorType = CLAY_ERROR_TYPE_INTERNAL_ERROR, 3990 .errorText = CLAY_STRING("Clay attempted to make an out of bounds array access. This is an internal error and is likely a bug."), 3991 .userData = context->errorHandler.userData }); 3992 return false; 3993 } 3994 3995 bool Clay__Array_AddCapacityCheck(int32_t length, int32_t capacity) 3996 { 3997 if (length < capacity) { 3998 return true; 3999 } 4000 Clay_Context* context = Clay_GetCurrentContext(); 4001 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 4002 .errorType = CLAY_ERROR_TYPE_INTERNAL_ERROR, 4003 .errorText = CLAY_STRING("Clay attempted to make an out of bounds array access. This is an internal error and is likely a bug."), 4004 .userData = context->errorHandler.userData }); 4005 return false; 4006 } 4007 4008 // PUBLIC API FROM HERE --------------------------------------- 4009 4010 CLAY_WASM_EXPORT("Clay_MinMemorySize") 4011 uint32_t Clay_MinMemorySize(void) { 4012 Clay_Context fakeContext = { 4013 .maxElementCount = Clay__defaultMaxElementCount, 4014 .maxMeasureTextCacheWordCount = Clay__defaultMaxMeasureTextWordCacheCount, 4015 .internalArena = { 4016 .capacity = SIZE_MAX, 4017 .memory = NULL, 4018 } 4019 }; 4020 Clay_Context* currentContext = Clay_GetCurrentContext(); 4021 if (currentContext) { 4022 fakeContext.maxElementCount = currentContext->maxElementCount; 4023 fakeContext.maxMeasureTextCacheWordCount = currentContext->maxMeasureTextCacheWordCount; 4024 } 4025 // Reserve space in the arena for the context, important for calculating min memory size correctly 4026 Clay__Context_Allocate_Arena(&fakeContext.internalArena); 4027 Clay__InitializePersistentMemory(&fakeContext); 4028 Clay__InitializeEphemeralMemory(&fakeContext); 4029 return (uint32_t)fakeContext.internalArena.nextAllocation + 128; 4030 } 4031 4032 CLAY_WASM_EXPORT("Clay_CreateArenaWithCapacityAndMemory") 4033 Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory) { 4034 Clay_Arena arena = { 4035 .capacity = capacity, 4036 .memory = (char *)memory 4037 }; 4038 return arena; 4039 } 4040 4041 #ifndef CLAY_WASM 4042 void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData) { 4043 Clay_Context* context = Clay_GetCurrentContext(); 4044 Clay__MeasureText = measureTextFunction; 4045 context->measureTextUserData = userData; 4046 } 4047 void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData) { 4048 Clay_Context* context = Clay_GetCurrentContext(); 4049 Clay__QueryScrollOffset = queryScrollOffsetFunction; 4050 context->queryScrollOffsetUserData = userData; 4051 } 4052 #endif 4053 4054 CLAY_WASM_EXPORT("Clay_SetLayoutDimensions") 4055 void Clay_SetLayoutDimensions(Clay_Dimensions dimensions) { 4056 Clay_Context* context = Clay_GetCurrentContext(); 4057 context->rootResizedLastFrame = !Clay__FloatEqual(context->layoutDimensions.width, dimensions.width) || !Clay__FloatEqual(context->layoutDimensions.height, dimensions.height); 4058 Clay_GetCurrentContext()->layoutDimensions = dimensions; 4059 } 4060 4061 CLAY_WASM_EXPORT("Clay_SetPointerState") 4062 void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { 4063 Clay_Context* context = Clay_GetCurrentContext(); 4064 if (context->booleanWarnings.maxElementsExceeded) { 4065 return; 4066 } 4067 context->pointerInfo.position = position; 4068 context->pointerOverIds.length = 0; 4069 Clay__int32_tArray dfsBuffer = context->layoutElementChildrenBuffer; 4070 for (int32_t rootIndex = context->layoutElementTreeRoots.length - 1; rootIndex >= 0; --rootIndex) { 4071 dfsBuffer.length = 0; 4072 Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); 4073 Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); 4074 context->treeNodeVisited.internalArray[0] = false; 4075 bool found = false; 4076 bool skipTree = false; 4077 while (dfsBuffer.length > 0) { 4078 if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { 4079 dfsBuffer.length--; 4080 continue; 4081 } 4082 context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; 4083 Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1)); 4084 // Skip mouse interactions on an element if it's currently transitioning, based on user config 4085 if (currentElement->config.transition.handler) { 4086 for (int I = 0; I < context->transitionDatas.length; ++I) { 4087 Clay__TransitionDataInternal* data = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, I); 4088 if (data->elementId == currentElement->id) { 4089 if (currentElement->config.transition.interactionHandling == CLAY_TRANSITION_DISABLE_INTERACTIONS_WHILE_TRANSITIONING_POSITION) { 4090 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)) { 4091 skipTree = true; 4092 } 4093 } else if (currentElement->config.transition.interactionHandling == CLAY_TRANSITION_ALLOW_INTERACTIONS_WHILE_TRANSITIONING_POSITION) { 4094 if (data->state == CLAY_TRANSITION_STATE_EXITING) { 4095 skipTree = true; 4096 } 4097 } 4098 } 4099 } 4100 } 4101 4102 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 4103 int32_t clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(currentElement - context->layoutElements.internalArray)); 4104 Clay_LayoutElementHashMapItem *clipItem = Clay__GetHashMapItem(clipElementId); 4105 if (mapItem && mapItem->generation > context->generation) { 4106 Clay_BoundingBox elementBox = mapItem->boundingBox; 4107 elementBox.x -= root->pointerOffset.x; 4108 elementBox.y -= root->pointerOffset.y; 4109 if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)) || context->externalScrollHandlingEnabled)) { 4110 if (!skipTree) { 4111 if (mapItem->onHoverFunction) { 4112 mapItem->onHoverFunction(mapItem->elementId, context->pointerInfo, mapItem->hoverFunctionUserData); 4113 } 4114 Clay_ElementIdArray_Add(&context->pointerOverIds, mapItem->elementId); 4115 } 4116 found = true; 4117 } 4118 if (skipTree || currentElement->isTextElement) { 4119 dfsBuffer.length--; 4120 continue; 4121 } 4122 for (int32_t i = currentElement->children.length - 1; i >= 0; --i) { 4123 Clay__int32_tArray_Add(&dfsBuffer, currentElement->children.elements[i]); 4124 context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked 4125 } 4126 } else { 4127 dfsBuffer.length--; 4128 } 4129 } 4130 4131 Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, root->layoutElementIndex); 4132 if (found && rootElement->config.floating.attachTo != CLAY_ATTACH_TO_NONE && rootElement->config.floating.pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_CAPTURE) { 4133 break; 4134 } 4135 } 4136 4137 if (isPointerDown) { 4138 if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { 4139 context->pointerInfo.state = CLAY_POINTER_DATA_PRESSED; 4140 } else if (context->pointerInfo.state != CLAY_POINTER_DATA_PRESSED) { 4141 context->pointerInfo.state = CLAY_POINTER_DATA_PRESSED_THIS_FRAME; 4142 } 4143 } else { 4144 if (context->pointerInfo.state == CLAY_POINTER_DATA_RELEASED_THIS_FRAME) { 4145 context->pointerInfo.state = CLAY_POINTER_DATA_RELEASED; 4146 } else if (context->pointerInfo.state != CLAY_POINTER_DATA_RELEASED) { 4147 context->pointerInfo.state = CLAY_POINTER_DATA_RELEASED_THIS_FRAME; 4148 } 4149 } 4150 } 4151 4152 CLAY_WASM_EXPORT("Clay_GetPointerState") 4153 CLAY_DLL_EXPORT Clay_PointerData Clay_GetPointerState(void) { 4154 return Clay_GetCurrentContext()->pointerInfo; 4155 } 4156 4157 CLAY_WASM_EXPORT("Clay_Initialize") 4158 Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler) { 4159 // Cacheline align memory passed in 4160 uintptr_t baseOffset = 64 - ((uintptr_t)arena.memory % 64); 4161 baseOffset = baseOffset == 64 ? 0 : baseOffset; 4162 arena.memory += baseOffset; 4163 Clay_Context *context = Clay__Context_Allocate_Arena(&arena); 4164 if (context == NULL) return NULL; 4165 // DEFAULTS 4166 Clay_Context *oldContext = Clay_GetCurrentContext(); 4167 *context = CLAY__INIT(Clay_Context) { 4168 .maxElementCount = oldContext ? oldContext->maxElementCount : Clay__defaultMaxElementCount, 4169 .maxMeasureTextCacheWordCount = oldContext ? oldContext->maxMeasureTextCacheWordCount : Clay__defaultMaxMeasureTextWordCacheCount, 4170 .errorHandler = errorHandler.errorHandlerFunction ? errorHandler : CLAY__INIT(Clay_ErrorHandler) { Clay__ErrorHandlerFunctionDefault, 0 }, 4171 .layoutDimensions = layoutDimensions, 4172 .internalArena = arena, 4173 }; 4174 Clay_SetCurrentContext(context); 4175 Clay__InitializePersistentMemory(context); 4176 Clay__InitializeEphemeralMemory(context); 4177 for (int32_t i = 0; i < context->layoutElementsHashMap.capacity; ++i) { 4178 context->layoutElementsHashMap.internalArray[i] = -1; 4179 } 4180 for (int32_t i = 0; i < context->measureTextHashMap.capacity; ++i) { 4181 context->measureTextHashMap.internalArray[i] = 0; 4182 } 4183 context->measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element" 4184 context->layoutDimensions = layoutDimensions; 4185 return context; 4186 } 4187 4188 CLAY_WASM_EXPORT("Clay_GetCurrentContext") 4189 Clay_Context* Clay_GetCurrentContext(void) { 4190 return Clay__currentContext; 4191 } 4192 4193 CLAY_WASM_EXPORT("Clay_SetCurrentContext") 4194 void Clay_SetCurrentContext(Clay_Context* context) { 4195 Clay__currentContext = context; 4196 } 4197 4198 CLAY_WASM_EXPORT("Clay_GetScrollOffset") 4199 Clay_Vector2 Clay_GetScrollOffset(void) { 4200 Clay_Context* context = Clay_GetCurrentContext(); 4201 if (context->booleanWarnings.maxElementsExceeded) { 4202 return CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; 4203 } 4204 Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); 4205 for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { 4206 Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); 4207 if (mapping->layoutElement == openLayoutElement) { 4208 return mapping->scrollPosition; 4209 } 4210 } 4211 return CLAY__INIT(Clay_Vector2) CLAY__DEFAULT_STRUCT; 4212 } 4213 4214 CLAY_WASM_EXPORT("Clay_UpdateScrollContainers") 4215 void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime) { 4216 Clay_Context* context = Clay_GetCurrentContext(); 4217 bool isPointerActive = enableDragScrolling && (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED || context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME); 4218 // Don't apply scroll events to ancestors of the inner element 4219 int32_t highestPriorityElementIndex = -1; 4220 Clay__ScrollContainerDataInternal *highestPriorityScrollData = CLAY__NULL; 4221 for (int32_t i = 0; i < context->scrollContainerDatas.length; i++) { 4222 Clay__ScrollContainerDataInternal *scrollData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); 4223 if (!scrollData->openThisFrame) { 4224 Clay__ScrollContainerDataInternalArray_RemoveSwapback(&context->scrollContainerDatas, i); 4225 continue; 4226 } 4227 scrollData->openThisFrame = false; 4228 Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(scrollData->elementId); 4229 // Element isn't rendered this frame but scroll offset has been retained 4230 if (!hashMapItem) { 4231 Clay__ScrollContainerDataInternalArray_RemoveSwapback(&context->scrollContainerDatas, i); 4232 continue; 4233 } 4234 4235 // Touch / click is released 4236 if (!isPointerActive && scrollData->pointerScrollActive) { 4237 float xDiff = scrollData->scrollPosition.x - scrollData->scrollOrigin.x; 4238 if (xDiff < -10 || xDiff > 10) { 4239 scrollData->scrollMomentum.x = (scrollData->scrollPosition.x - scrollData->scrollOrigin.x) / (scrollData->momentumTime * 25); 4240 } 4241 float yDiff = scrollData->scrollPosition.y - scrollData->scrollOrigin.y; 4242 if (yDiff < -10 || yDiff > 10) { 4243 scrollData->scrollMomentum.y = (scrollData->scrollPosition.y - scrollData->scrollOrigin.y) / (scrollData->momentumTime * 25); 4244 } 4245 scrollData->pointerScrollActive = false; 4246 4247 scrollData->pointerOrigin = CLAY__INIT(Clay_Vector2){0,0}; 4248 scrollData->scrollOrigin = CLAY__INIT(Clay_Vector2){0,0}; 4249 scrollData->momentumTime = 0; 4250 } 4251 4252 // Apply existing momentum 4253 scrollData->scrollPosition.x += scrollData->scrollMomentum.x; 4254 scrollData->scrollMomentum.x *= 0.95f; 4255 bool scrollOccurred = scrollDelta.x != 0 || scrollDelta.y != 0; 4256 if ((scrollData->scrollMomentum.x > -0.1f && scrollData->scrollMomentum.x < 0.1f) || scrollOccurred) { 4257 scrollData->scrollMomentum.x = 0; 4258 } 4259 scrollData->scrollPosition.x = CLAY__MIN(CLAY__MAX(scrollData->scrollPosition.x, -(CLAY__MAX(scrollData->contentSize.width - scrollData->layoutElement->dimensions.width, 0))), 0); 4260 4261 scrollData->scrollPosition.y += scrollData->scrollMomentum.y; 4262 scrollData->scrollMomentum.y *= 0.95f; 4263 if ((scrollData->scrollMomentum.y > -0.1f && scrollData->scrollMomentum.y < 0.1f) || scrollOccurred) { 4264 scrollData->scrollMomentum.y = 0; 4265 } 4266 scrollData->scrollPosition.y = CLAY__MIN(CLAY__MAX(scrollData->scrollPosition.y, -(CLAY__MAX(scrollData->contentSize.height - scrollData->layoutElement->dimensions.height, 0))), 0); 4267 4268 for (int32_t j = 0; j < context->pointerOverIds.length; ++j) { // TODO n & m are small here but this being n*m gives me the creeps 4269 if (scrollData->layoutElement->id == Clay_ElementIdArray_Get(&context->pointerOverIds, j)->id) { 4270 highestPriorityElementIndex = j; 4271 highestPriorityScrollData = scrollData; 4272 } 4273 } 4274 } 4275 4276 if (highestPriorityElementIndex > -1 && highestPriorityScrollData) { 4277 Clay_LayoutElement *scrollElement = highestPriorityScrollData->layoutElement; 4278 Clay_ClipElementConfig *clipConfig = &scrollElement->config.clip; 4279 bool canScrollVertically = clipConfig->vertical && highestPriorityScrollData->contentSize.height > scrollElement->dimensions.height; 4280 bool canScrollHorizontally = clipConfig->horizontal && highestPriorityScrollData->contentSize.width > scrollElement->dimensions.width; 4281 // Handle wheel scroll 4282 if (canScrollVertically) { 4283 highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollPosition.y + scrollDelta.y * 10; 4284 } 4285 if (canScrollHorizontally) { 4286 highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollPosition.x + scrollDelta.x * 10; 4287 } 4288 // Handle click / touch scroll 4289 if (isPointerActive) { 4290 highestPriorityScrollData->scrollMomentum = CLAY__INIT(Clay_Vector2)CLAY__DEFAULT_STRUCT; 4291 if (!highestPriorityScrollData->pointerScrollActive) { 4292 highestPriorityScrollData->pointerOrigin = context->pointerInfo.position; 4293 highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition; 4294 highestPriorityScrollData->pointerScrollActive = true; 4295 } else { 4296 float scrollDeltaX = 0, scrollDeltaY = 0; 4297 if (canScrollHorizontally) { 4298 float oldXScrollPosition = highestPriorityScrollData->scrollPosition.x; 4299 highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollOrigin.x + (context->pointerInfo.position.x - highestPriorityScrollData->pointerOrigin.x); 4300 highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - highestPriorityScrollData->boundingBox.width)); 4301 scrollDeltaX = highestPriorityScrollData->scrollPosition.x - oldXScrollPosition; 4302 } 4303 if (canScrollVertically) { 4304 float oldYScrollPosition = highestPriorityScrollData->scrollPosition.y; 4305 highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollOrigin.y + (context->pointerInfo.position.y - highestPriorityScrollData->pointerOrigin.y); 4306 highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - highestPriorityScrollData->boundingBox.height)); 4307 scrollDeltaY = highestPriorityScrollData->scrollPosition.y - oldYScrollPosition; 4308 } 4309 if (scrollDeltaX > -0.1f && scrollDeltaX < 0.1f && scrollDeltaY > -0.1f && scrollDeltaY < 0.1f && highestPriorityScrollData->momentumTime > 0.15f) { 4310 highestPriorityScrollData->momentumTime = 0; 4311 highestPriorityScrollData->pointerOrigin = context->pointerInfo.position; 4312 highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition; 4313 } else { 4314 highestPriorityScrollData->momentumTime += deltaTime; 4315 } 4316 } 4317 } 4318 // Clamp any changes to scroll position to the maximum size of the contents 4319 if (canScrollVertically) { 4320 highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - scrollElement->dimensions.height)); 4321 } 4322 if (canScrollHorizontally) { 4323 highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - scrollElement->dimensions.width)); 4324 } 4325 } 4326 } 4327 4328 CLAY_WASM_EXPORT("Clay_BeginLayout") 4329 void Clay_BeginLayout(void) { 4330 Clay_Context* context = Clay_GetCurrentContext(); 4331 Clay__InitializeEphemeralMemory(context); 4332 context->generation++; 4333 context->dynamicElementIndex = 0; 4334 // Set up the root container that covers the entire window 4335 Clay_Dimensions rootDimensions = {context->layoutDimensions.width, context->layoutDimensions.height}; 4336 if (context->debugModeEnabled) { 4337 rootDimensions.width -= (float)Clay__debugViewWidth; 4338 } 4339 context->booleanWarnings = CLAY__INIT(Clay_BooleanWarnings) CLAY__DEFAULT_STRUCT; 4340 Clay__OpenElementWithId(CLAY_ID("Clay__RootContainer")); 4341 Clay__ConfigureOpenElement(CLAY__INIT(Clay_ElementDeclaration) { 4342 .layout = { .sizing = {CLAY_SIZING_FIXED((rootDimensions.width)), CLAY_SIZING_FIXED(rootDimensions.height)} } 4343 }); 4344 Clay__int32_tArray_Add(&context->openLayoutElementStack, 0); 4345 Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { .layoutElementIndex = 0 }); 4346 } 4347 4348 void Clay__CloneElementsWithExitTransition() { 4349 Clay_Context* context = Clay_GetCurrentContext(); 4350 int32_t nextIndex = context->layoutElements.capacity - 1; 4351 int32_t nextChildIndex = context->layoutElementChildren.capacity - 1; 4352 4353 for (int i = 0; i < context->transitionDatas.length; ++i) { 4354 Clay__TransitionDataInternal *data = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, i); 4355 Clay_TransitionElementConfig* config = &data->elementThisFrame->config.transition; 4356 if (data->transitionOut) { 4357 Clay__int32_tArray bfsBuffer = context->openLayoutElementStack; 4358 bfsBuffer.length = 0; 4359 Clay_LayoutElement* newElement = Clay_LayoutElementArray_Set_DontTouchLength(&context->layoutElements, nextIndex, *data->elementThisFrame); 4360 Clay__StringArray_Set_DontTouchLength(&context->layoutElementIdStrings, nextIndex, *Clay__StringArray_GetCheckCapacity(&context->layoutElementIdStrings, data->elementThisFrame - context->layoutElements.internalArray)); 4361 Clay__int32_tArray_Add(&bfsBuffer, nextIndex); 4362 data->elementThisFrame = newElement; 4363 nextIndex--; 4364 4365 int32_t bufferIndex = 0; 4366 while(bufferIndex < bfsBuffer.length) { 4367 Clay_LayoutElement *layoutElement = Clay_LayoutElementArray_GetCheckCapacity(&context->layoutElements, Clay__int32_tArray_GetValue(&bfsBuffer, bufferIndex)); 4368 bufferIndex++; 4369 for (int j = layoutElement->children.length - 1; j >= 0; --j) { 4370 Clay_LayoutElement* childElement = Clay_LayoutElementArray_GetCheckCapacity(&context->layoutElements, layoutElement->children.elements[j]); 4371 Clay__int32_tArray_Add(&bfsBuffer, nextIndex); 4372 Clay_LayoutElement* newChildElement = Clay_LayoutElementArray_Set_DontTouchLength(&context->layoutElements, nextIndex, *childElement); 4373 Clay__StringArray_Set_DontTouchLength(&context->layoutElementIdStrings, nextIndex, *Clay__StringArray_GetCheckCapacity(&context->layoutElementIdStrings, childElement - context->layoutElements.internalArray)); 4374 Clay__int32_tArray_Set_DontTouchLength(&context->layoutElementChildren, nextChildIndex, nextIndex); 4375 nextIndex--; 4376 nextChildIndex--; 4377 } 4378 layoutElement->children.elements = &context->layoutElementChildren.internalArray[nextChildIndex + 1]; 4379 } 4380 } 4381 } 4382 }; 4383 4384 void Clay_ApplyTransitionedPropertiesToElement(Clay_LayoutElement* currentElement, Clay_TransitionProperty properties, Clay_TransitionData currentTransitionData, Clay_BoundingBox* boundingBox, bool reparented) { 4385 if (properties & CLAY_TRANSITION_PROPERTY_WIDTH) { 4386 if (!reparented) { 4387 currentElement->dimensions.width = currentTransitionData.boundingBox.width; 4388 currentElement->config.layout.sizing.width = CLAY_SIZING_FIXED(currentTransitionData.boundingBox.width); 4389 } else { 4390 boundingBox->width = currentTransitionData.boundingBox.width; 4391 } 4392 } 4393 if (properties & CLAY_TRANSITION_PROPERTY_HEIGHT) { 4394 if (!reparented) { 4395 currentElement->dimensions.height = currentTransitionData.boundingBox.height; 4396 currentElement->config.layout.sizing.height = CLAY_SIZING_FIXED(currentTransitionData.boundingBox.height); 4397 } else { 4398 boundingBox->height = currentTransitionData.boundingBox.height; 4399 } 4400 } 4401 if (properties & CLAY_TRANSITION_PROPERTY_X) { 4402 boundingBox->x = currentTransitionData.boundingBox.x; 4403 } 4404 if (properties & CLAY_TRANSITION_PROPERTY_Y) { 4405 boundingBox->y = currentTransitionData.boundingBox.y; 4406 } 4407 if (properties & CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR) { 4408 currentElement->config.overlayColor = currentTransitionData.overlayColor; 4409 } 4410 if (properties & CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR) { 4411 currentElement->config.backgroundColor = currentTransitionData.backgroundColor; 4412 } 4413 if (properties & CLAY_TRANSITION_PROPERTY_BORDER_COLOR) { 4414 currentElement->config.border.color = currentTransitionData.borderColor; 4415 } 4416 if (properties & CLAY_TRANSITION_PROPERTY_BORDER_WIDTH) { 4417 currentElement->config.border.width = currentTransitionData.borderWidth; 4418 } 4419 } 4420 4421 void Clay__CreateDebugView() { 4422 4423 } 4424 4425 CLAY_WASM_EXPORT("Clay_EndLayout") 4426 Clay_RenderCommandArray Clay_EndLayout(float deltaTime) { 4427 Clay_Context* context = Clay_GetCurrentContext(); 4428 Clay__CloseElement(); 4429 4430 for (int i = 0; i < context->transitionDatas.length; ++i) { 4431 Clay__TransitionDataInternal *data = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, i); 4432 Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(data->elementId); 4433 // 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? 4434 // 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 4435 // will have simply disappeared completely at this point, and there will be no element through which to access the config. 4436 if (data->transitionOut) { 4437 Clay_TransitionElementConfig* config = &data->elementThisFrame->config.transition; 4438 // Element wasn't found this frame - either delete transition data or transition out 4439 if (hashMapItem->generation <= context->generation) { 4440 Clay_LayoutElementHashMapItem *parentHashMapItem = Clay__GetHashMapItem(data->parentId); 4441 // Don't exit transition if the parent has also exited and SKIP_WHEN_PARENT_EXITS is used 4442 if (config->exit.trigger == CLAY_TRANSITION_EXIT_TRIGGER_WHEN_PARENT_EXITS || !parentHashMapItem || parentHashMapItem->generation > context->generation) { 4443 if (data->state != CLAY_TRANSITION_STATE_EXITING) { 4444 if (parentHashMapItem->generation <= context->generation) { 4445 data->elementThisFrame->config.floating.attachTo = CLAY_ATTACH_TO_ROOT; 4446 data->elementThisFrame->config.floating.offset = CLAY__INIT(Clay_Vector2) { hashMapItem->boundingBox.x, hashMapItem->boundingBox.y }; 4447 } 4448 data->elementThisFrame->exiting = true; 4449 data->elementThisFrame->config.layout.sizing.width = CLAY_SIZING_FIXED(data->elementThisFrame->dimensions.width); 4450 data->elementThisFrame->config.layout.sizing.height = CLAY_SIZING_FIXED(data->elementThisFrame->dimensions.height); 4451 data->state = CLAY_TRANSITION_STATE_EXITING; 4452 data->activeProperties = config->properties; 4453 data->elapsedTime = 0; 4454 data->targetState = config->exit.setFinalState(data->targetState, config->properties); 4455 } 4456 4457 // Clone the entire subtree back into the main UI layout tree 4458 Clay__int32_tArray bfsBuffer = context->openLayoutElementStack; 4459 bfsBuffer.length = 0; 4460 data->elementThisFrame = Clay_LayoutElementArray_Add(&context->layoutElements, *data->elementThisFrame); 4461 int32_t exitingElementIndex = data->elementThisFrame - context->layoutElements.internalArray; 4462 Clay__StringArray_Add(&context->layoutElementIdStrings, *Clay__StringArray_GetCheckCapacity(&context->layoutElementIdStrings, exitingElementIndex)); 4463 Clay__int32_tArray_Add(&context->layoutElementClipElementIds, *Clay__int32_tArray_GetCheckCapacity(&context->layoutElementClipElementIds, exitingElementIndex)); 4464 Clay__int32_tArray_Add(&bfsBuffer, exitingElementIndex); 4465 hashMapItem->layoutElement = data->elementThisFrame; 4466 hashMapItem->generation = context->generation + 1; 4467 int32_t bufferIndex = 0; 4468 while (bufferIndex < bfsBuffer.length) { 4469 Clay_LayoutElement *layoutElement = Clay_LayoutElementArray_GetCheckCapacity(&context->layoutElements, Clay__int32_tArray_GetValue(&bfsBuffer, bufferIndex)); 4470 bufferIndex++; 4471 int32_t firstChildSlot = context->layoutElementChildren.length; 4472 for (int j = 0; j < layoutElement->children.length; ++j) { 4473 Clay_LayoutElement* childElement = Clay_LayoutElementArray_GetCheckCapacity(&context->layoutElements, layoutElement->children.elements[j]); 4474 int32_t childElementIndex = childElement - context->layoutElements.internalArray; 4475 Clay_LayoutElement* newChildElement = Clay_LayoutElementArray_Add(&context->layoutElements, *childElement); 4476 Clay__StringArray_Add(&context->layoutElementIdStrings, *Clay__StringArray_GetCheckCapacity(&context->layoutElementIdStrings, childElementIndex)); 4477 Clay__int32_tArray_Add(&context->layoutElementClipElementIds, *Clay__int32_tArray_GetCheckCapacity(&context->layoutElementClipElementIds, childElementIndex)); 4478 Clay__int32_tArray_Add(&bfsBuffer, context->layoutElements.length - 1); 4479 if (newChildElement->isTextElement) { 4480 newChildElement->textElementData.wrappedLines.length = 0; 4481 } 4482 Clay__int32_tArray_Add(&context->layoutElementChildren, context->layoutElements.length - 1); 4483 } 4484 layoutElement->children.elements = &context->layoutElementChildren.internalArray[firstChildSlot]; 4485 } 4486 4487 // Reattach the inserted subtree to its previous parent if it still exists 4488 if (parentHashMapItem->generation > context->generation) { 4489 Clay_LayoutElement *parentElement = parentHashMapItem->layoutElement; 4490 int32_t newChildrenStartIndex = context->layoutElementChildren.length; 4491 bool found = false; 4492 if (config->exit.siblingOrdering == CLAY_EXIT_TRANSITION_ORDERING_UNDERNEATH_SIBLINGS) { 4493 Clay__int32_tArray_Add(&context->layoutElementChildren, exitingElementIndex); 4494 found = true; 4495 } 4496 for (int j = 0; j < parentElement->children.length; ++j) { 4497 if (config->exit.siblingOrdering == CLAY_EXIT_TRANSITION_ORDERING_NATURAL_ORDER && j == data->siblingIndex) { 4498 Clay__int32_tArray_Add(&context->layoutElementChildren, exitingElementIndex); 4499 found = true; 4500 } 4501 Clay__int32_tArray_Add(&context->layoutElementChildren, parentElement->children.elements[j]); 4502 } 4503 if (!found) { 4504 Clay__int32_tArray_Add(&context->layoutElementChildren, exitingElementIndex); 4505 } 4506 parentElement->children.length++; 4507 parentElement->children.elements = &context->layoutElementChildren.internalArray[newChildrenStartIndex]; 4508 // Otherwise, just attach to the root as a floating element 4509 } else { 4510 Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { 4511 .layoutElementIndex = (int32_t)(data->elementThisFrame - context->layoutElements.internalArray), 4512 .parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0).id, 4513 .zIndex = 1, 4514 }); 4515 } 4516 // Parent exited, just delete child without exit transition 4517 } else { 4518 Clay__TransitionDataInternalArray_RemoveSwapback(&context->transitionDatas, i); 4519 i--; 4520 continue; 4521 } 4522 } 4523 // Transition element exited and doesn't have an exit handler defined 4524 // Or, the user deleted the transition handler from one frame to the next 4525 } else if (hashMapItem->generation <= context->generation || !hashMapItem->layoutElement->config.transition.handler) { 4526 Clay__TransitionDataInternalArray_RemoveSwapback(&context->transitionDatas, i); 4527 i--; 4528 continue; 4529 } 4530 } 4531 4532 if (context->booleanWarnings.maxElementsExceeded) { 4533 Clay_String message; 4534 message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount"); 4535 Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { 4536 .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, 4537 .renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .textColor = {255, 0, 0, 255}, .fontSize = 16 } }, 4538 .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT 4539 }); 4540 } else { 4541 if (context->transitionDatas.length > 0) { 4542 Clay__CalculateFinalLayout(deltaTime, false, false); 4543 4544 for (int i = 0; i < context->transitionDatas.length; ++i) { 4545 Clay__TransitionDataInternal* transitionData = Clay__TransitionDataInternalArray_Get(&context->transitionDatas, i); 4546 Clay_LayoutElement* currentElement = transitionData->elementThisFrame; 4547 Clay_LayoutElementHashMapItem* mapItem = Clay__GetHashMapItem(transitionData->elementId); 4548 Clay_LayoutElementHashMapItem* parentMapItem = Clay__GetHashMapItem(transitionData->parentId); 4549 Clay_TransitionData targetState = transitionData->targetState; 4550 if (transitionData->state != CLAY_TRANSITION_STATE_EXITING) { 4551 targetState = CLAY__INIT(Clay_TransitionData) { 4552 mapItem->boundingBox, 4553 currentElement->config.backgroundColor, 4554 currentElement->config.overlayColor, 4555 currentElement->config.border.color, 4556 currentElement->config.border.width, 4557 }; 4558 } 4559 Clay_TransitionData oldTargetState = transitionData->targetState; 4560 transitionData->targetState = targetState; 4561 if (mapItem->appearedThisFrame) { 4562 if (currentElement->config.transition.enter.setInitialState && !(parentMapItem->appearedThisFrame && currentElement->config.transition.enter.trigger == CLAY_TRANSITION_ENTER_SKIP_ON_FIRST_PARENT_FRAME)) { 4563 transitionData->state = CLAY_TRANSITION_STATE_ENTERING; 4564 transitionData->initialState = currentElement->config.transition.enter.setInitialState(transitionData->targetState, currentElement->config.transition.properties); 4565 transitionData->currentState = transitionData->initialState; 4566 Clay_ApplyTransitionedPropertiesToElement(currentElement, currentElement->config.transition.properties, transitionData->initialState, &mapItem->boundingBox, transitionData->reparented); 4567 } else { 4568 transitionData->initialState = targetState; 4569 transitionData->currentState = targetState; 4570 } 4571 } else { 4572 Clay_Vector2 parentScrollOffset = parentMapItem->layoutElement->config.clip.childOffset; 4573 Clay_Vector2 newRelativePosition = { 4574 mapItem->boundingBox.x - parentMapItem->boundingBox.x - parentScrollOffset.x, 4575 mapItem->boundingBox.y - parentMapItem->boundingBox.y - parentScrollOffset.y, 4576 }; 4577 Clay_Vector2 oldRelativePosition = transitionData->oldParentRelativePosition; 4578 transitionData->oldParentRelativePosition = newRelativePosition; 4579 Clay_TransitionProperty properties = currentElement->config.transition.properties; 4580 int32_t activeProperties = CLAY_TRANSITION_PROPERTY_NONE; 4581 if (properties & CLAY_TRANSITION_PROPERTY_X) { 4582 if (!Clay__FloatEqual(oldTargetState.boundingBox.x, targetState.boundingBox.x) && !(Clay__FloatEqual(oldRelativePosition.x, newRelativePosition.x)) && !context->rootResizedLastFrame) { 4583 activeProperties |= CLAY_TRANSITION_PROPERTY_X; 4584 } 4585 } 4586 if (properties & CLAY_TRANSITION_PROPERTY_Y) { 4587 if (!Clay__FloatEqual(oldTargetState.boundingBox.y, targetState.boundingBox.y) && !(Clay__FloatEqual(oldRelativePosition.y, newRelativePosition.y)) && !context->rootResizedLastFrame) { 4588 activeProperties |= CLAY_TRANSITION_PROPERTY_Y; 4589 } 4590 } 4591 if (properties & CLAY_TRANSITION_PROPERTY_WIDTH) { 4592 if (!Clay__FloatEqual(oldTargetState.boundingBox.width, targetState.boundingBox.width) && !context->rootResizedLastFrame) { 4593 activeProperties |= CLAY_TRANSITION_PROPERTY_WIDTH; 4594 } 4595 } 4596 if (properties & CLAY_TRANSITION_PROPERTY_HEIGHT) { 4597 if (!Clay__FloatEqual(oldTargetState.boundingBox.height, targetState.boundingBox.height) && !context->rootResizedLastFrame) { 4598 activeProperties |= CLAY_TRANSITION_PROPERTY_HEIGHT; 4599 } 4600 } 4601 if (properties & CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR) { 4602 if (!Clay__MemCmp((char *) &oldTargetState.backgroundColor, (char *)&targetState.backgroundColor, sizeof(Clay_Color))) { 4603 activeProperties |= CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR; 4604 } 4605 } 4606 if (properties & CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR) { 4607 if (!Clay__MemCmp((char *) &oldTargetState.overlayColor, (char *)&targetState.overlayColor, sizeof(Clay_Color))) { 4608 activeProperties |= CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR; 4609 } 4610 } 4611 if (properties & CLAY_TRANSITION_PROPERTY_BORDER_COLOR) { 4612 if (!Clay__MemCmp((char *) &oldTargetState.borderColor, (char *)&targetState.borderColor, sizeof(Clay_Color))) { 4613 activeProperties |= CLAY_TRANSITION_PROPERTY_BORDER_COLOR; 4614 } 4615 } 4616 if (properties & CLAY_TRANSITION_PROPERTY_BORDER_WIDTH) { 4617 if (!Clay__MemCmp((char *) &oldTargetState.borderWidth, (char *)&targetState.borderWidth, sizeof(Clay_BorderWidth))) { 4618 activeProperties |= CLAY_TRANSITION_PROPERTY_BORDER_WIDTH; 4619 } 4620 } 4621 4622 if (activeProperties != 0 && transitionData->state != CLAY_TRANSITION_STATE_EXITING) { 4623 transitionData->elapsedTime = 0; 4624 transitionData->initialState = transitionData->currentState; 4625 transitionData->state = CLAY_TRANSITION_STATE_TRANSITIONING; 4626 transitionData->activeProperties = (Clay_TransitionProperty)activeProperties; 4627 } 4628 4629 if (transitionData->state == CLAY_TRANSITION_STATE_IDLE) { 4630 transitionData->initialState = targetState; 4631 transitionData->currentState = targetState; 4632 transitionData->targetState = targetState; 4633 } else { 4634 bool transitionComplete = true; 4635 transitionComplete = currentElement->config.transition.handler(CLAY__INIT(Clay_TransitionCallbackArguments) { 4636 transitionData->state, 4637 transitionData->initialState, 4638 &transitionData->currentState, 4639 targetState, 4640 transitionData->elapsedTime, 4641 currentElement->config.transition.duration, 4642 currentElement->config.transition.properties 4643 }); 4644 4645 Clay_ApplyTransitionedPropertiesToElement(currentElement, currentElement->config.transition.properties, transitionData->currentState, &mapItem->boundingBox, transitionData->reparented); 4646 transitionData->elapsedTime += deltaTime; 4647 4648 if (transitionComplete) { 4649 if (transitionData->state == CLAY_TRANSITION_STATE_ENTERING || transitionData->state == CLAY_TRANSITION_STATE_TRANSITIONING) {transitionData->state = CLAY_TRANSITION_STATE_IDLE; 4650 transitionData->elapsedTime = 0; 4651 transitionData->reparented = false; 4652 transitionData->activeProperties = CLAY_TRANSITION_PROPERTY_NONE; 4653 } else if (transitionData->state == CLAY_TRANSITION_STATE_EXITING) { 4654 Clay__TransitionDataInternalArray_RemoveSwapback(&context->transitionDatas, i); 4655 } 4656 } 4657 } 4658 } 4659 } 4660 4661 if (context->debugModeEnabled) { 4662 context->warningsEnabled = false; 4663 Clay__RenderDebugView(); 4664 context->warningsEnabled = true; 4665 } 4666 4667 if (context->booleanWarnings.maxElementsExceeded) { 4668 Clay_String message; 4669 message = CLAY_STRING("Clay Error: Debug view caused layout element count to exceed Clay__maxElementCount"); 4670 Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { 4671 .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, 4672 .renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .textColor = {255, 0, 0, 255}, .fontSize = 16 } }, 4673 .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT 4674 }); 4675 } else { 4676 Clay__CalculateFinalLayout(deltaTime, true, true); 4677 Clay__CloneElementsWithExitTransition(); 4678 } 4679 } else { 4680 if (context->debugModeEnabled) { 4681 context->warningsEnabled = false; 4682 Clay__RenderDebugView(); 4683 context->warningsEnabled = true; 4684 } 4685 4686 if (context->booleanWarnings.maxElementsExceeded) { 4687 Clay_String message; 4688 message = CLAY_STRING("Clay Error: Debug view caused layout element count to exceed Clay__maxElementCount"); 4689 Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { 4690 .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, 4691 .renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .textColor = {255, 0, 0, 255}, .fontSize = 16 } }, 4692 .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT 4693 }); 4694 } else { 4695 Clay__CalculateFinalLayout(deltaTime, false, true); 4696 } 4697 } 4698 } 4699 if (context->openLayoutElementStack.length > 1) { 4700 context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { 4701 .errorType = CLAY_ERROR_TYPE_UNBALANCED_OPEN_CLOSE, 4702 .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."), 4703 .userData = context->errorHandler.userData }); 4704 } 4705 return context->renderCommands; 4706 } 4707 4708 CLAY_WASM_EXPORT("Clay_GetOpenElementId") 4709 uint32_t Clay_GetOpenElementId(void) { 4710 return Clay__GetOpenLayoutElement()->id; 4711 } 4712 4713 CLAY_WASM_EXPORT("Clay_GetElementId") 4714 Clay_ElementId Clay_GetElementId(Clay_String idString) { 4715 return Clay__HashString(idString, 0); 4716 } 4717 4718 CLAY_WASM_EXPORT("Clay_GetElementIdWithIndex") 4719 Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index) { 4720 return Clay__HashStringWithOffset(idString, index, 0); 4721 } 4722 4723 bool Clay_Hovered(void) { 4724 Clay_Context* context = Clay_GetCurrentContext(); 4725 if (context->booleanWarnings.maxElementsExceeded) { 4726 return false; 4727 } 4728 Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); 4729 for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { 4730 if (Clay_ElementIdArray_Get(&context->pointerOverIds, i)->id == openLayoutElement->id) { 4731 return true; 4732 } 4733 } 4734 return false; 4735 } 4736 4737 void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData), void *userData) { 4738 Clay_Context* context = Clay_GetCurrentContext(); 4739 if (context->booleanWarnings.maxElementsExceeded) { 4740 return; 4741 } 4742 Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); 4743 Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(openLayoutElement->id); 4744 hashMapItem->onHoverFunction = onHoverFunction; 4745 hashMapItem->hoverFunctionUserData = userData; 4746 } 4747 4748 CLAY_WASM_EXPORT("Clay_PointerOver") 4749 bool Clay_PointerOver(Clay_ElementId elementId) { // TODO return priority for separating multiple results 4750 Clay_Context* context = Clay_GetCurrentContext(); 4751 for (int32_t i = 0; i < context->pointerOverIds.length; ++i) { 4752 if (Clay_ElementIdArray_Get(&context->pointerOverIds, i)->id == elementId.id) { 4753 return true; 4754 } 4755 } 4756 return false; 4757 } 4758 4759 CLAY_WASM_EXPORT("Clay_GetScrollContainerData") 4760 Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id) { 4761 Clay_Context* context = Clay_GetCurrentContext(); 4762 for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) { 4763 Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i); 4764 if (scrollContainerData->elementId == id.id) { 4765 if (!scrollContainerData->layoutElement) { // This can happen on the first frame before a scroll container is declared 4766 return CLAY__INIT(Clay_ScrollContainerData) CLAY__DEFAULT_STRUCT; 4767 } 4768 return CLAY__INIT(Clay_ScrollContainerData) { 4769 .scrollPosition = &scrollContainerData->scrollPosition, 4770 .scrollContainerDimensions = { scrollContainerData->boundingBox.width, scrollContainerData->boundingBox.height }, 4771 .contentDimensions = scrollContainerData->contentSize, 4772 .config = scrollContainerData->layoutElement->config.clip, 4773 .found = true 4774 }; 4775 } 4776 } 4777 return CLAY__INIT(Clay_ScrollContainerData) CLAY__DEFAULT_STRUCT; 4778 } 4779 4780 CLAY_WASM_EXPORT("Clay_GetElementData") 4781 Clay_ElementData Clay_GetElementData(Clay_ElementId id){ 4782 Clay_LayoutElementHashMapItem * item = Clay__GetHashMapItem(id.id); 4783 if(item == &Clay_LayoutElementHashMapItem_DEFAULT) { 4784 return CLAY__INIT(Clay_ElementData) CLAY__DEFAULT_STRUCT; 4785 } 4786 4787 return CLAY__INIT(Clay_ElementData){ 4788 .boundingBox = item->boundingBox, 4789 .found = true 4790 }; 4791 } 4792 4793 CLAY_WASM_EXPORT("Clay_SetDebugModeEnabled") 4794 void Clay_SetDebugModeEnabled(bool enabled) { 4795 Clay_Context* context = Clay_GetCurrentContext(); 4796 context->debugModeEnabled = enabled; 4797 } 4798 4799 CLAY_WASM_EXPORT("Clay_IsDebugModeEnabled") 4800 bool Clay_IsDebugModeEnabled(void) { 4801 Clay_Context* context = Clay_GetCurrentContext(); 4802 return context->debugModeEnabled; 4803 } 4804 4805 CLAY_WASM_EXPORT("Clay_SetCullingEnabled") 4806 void Clay_SetCullingEnabled(bool enabled) { 4807 Clay_Context* context = Clay_GetCurrentContext(); 4808 context->disableCulling = !enabled; 4809 } 4810 4811 CLAY_WASM_EXPORT("Clay_SetExternalScrollHandlingEnabled") 4812 void Clay_SetExternalScrollHandlingEnabled(bool enabled) { 4813 Clay_Context* context = Clay_GetCurrentContext(); 4814 context->externalScrollHandlingEnabled = enabled; 4815 } 4816 4817 CLAY_WASM_EXPORT("Clay_GetMaxElementCount") 4818 int32_t Clay_GetMaxElementCount(void) { 4819 Clay_Context* context = Clay_GetCurrentContext(); 4820 return context->maxElementCount; 4821 } 4822 4823 CLAY_WASM_EXPORT("Clay_SetMaxElementCount") 4824 void Clay_SetMaxElementCount(int32_t maxElementCount) { 4825 Clay_Context* context = Clay_GetCurrentContext(); 4826 if (context) { 4827 context->maxElementCount = maxElementCount; 4828 } else { 4829 Clay__defaultMaxElementCount = maxElementCount; // TODO: Fix this 4830 Clay__defaultMaxMeasureTextWordCacheCount = maxElementCount * 2; 4831 } 4832 } 4833 4834 CLAY_WASM_EXPORT("Clay_GetMaxMeasureTextCacheWordCount") 4835 int32_t Clay_GetMaxMeasureTextCacheWordCount(void) { 4836 Clay_Context* context = Clay_GetCurrentContext(); 4837 return context->maxMeasureTextCacheWordCount; 4838 } 4839 4840 CLAY_WASM_EXPORT("Clay_SetMaxMeasureTextCacheWordCount") 4841 void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount) { 4842 Clay_Context* context = Clay_GetCurrentContext(); 4843 if (context) { 4844 Clay__currentContext->maxMeasureTextCacheWordCount = maxMeasureTextCacheWordCount; 4845 } else { 4846 Clay__defaultMaxMeasureTextWordCacheCount = maxMeasureTextCacheWordCount; // TODO: Fix this 4847 } 4848 } 4849 4850 CLAY_WASM_EXPORT("Clay_ResetMeasureTextCache") 4851 void Clay_ResetMeasureTextCache(void) { 4852 Clay_Context* context = Clay_GetCurrentContext(); 4853 context->measureTextHashMapInternal.length = 0; 4854 context->measureTextHashMapInternalFreeList.length = 0; 4855 context->measureTextHashMap.length = 0; 4856 context->measuredWords.length = 0; 4857 context->measuredWordsFreeList.length = 0; 4858 4859 for (int32_t i = 0; i < context->measureTextHashMap.capacity; ++i) { 4860 context->measureTextHashMap.internalArray[i] = 0; 4861 } 4862 context->measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element" 4863 } 4864 4865 #define CLAY__LERP(from, to, mix) (from + (to - from) * mix) 4866 4867 CLAY_DLL_EXPORT bool Clay_EaseOut(Clay_TransitionCallbackArguments arguments) { 4868 float ratio = 1; 4869 if (arguments.duration > 0) { 4870 ratio = CLAY__MIN(arguments.elapsedTime / arguments.duration, 1); 4871 } 4872 float inverse = 1.0f - ratio; 4873 float lerpAmount = 1.0f - (inverse * inverse * inverse); 4874 if (arguments.properties & CLAY_TRANSITION_PROPERTY_X) { 4875 arguments.current->boundingBox.x = CLAY__LERP(arguments.initial.boundingBox.x, arguments.target.boundingBox.x, lerpAmount); 4876 } 4877 if (arguments.properties & CLAY_TRANSITION_PROPERTY_Y) { 4878 arguments.current->boundingBox.y = CLAY__LERP(arguments.initial.boundingBox.y, arguments.target.boundingBox.y, lerpAmount); 4879 } 4880 if (arguments.properties & CLAY_TRANSITION_PROPERTY_WIDTH) { 4881 arguments.current->boundingBox.width = CLAY__LERP(arguments.initial.boundingBox.width, arguments.target.boundingBox.width, lerpAmount); 4882 } 4883 if (arguments.properties & CLAY_TRANSITION_PROPERTY_HEIGHT) { 4884 arguments.current->boundingBox.height = CLAY__LERP(arguments.initial.boundingBox.height, arguments.target.boundingBox.height, lerpAmount); 4885 } 4886 if (arguments.properties & CLAY_TRANSITION_PROPERTY_BACKGROUND_COLOR) { 4887 arguments.current->backgroundColor = CLAY__INIT(Clay_Color) { 4888 .r = CLAY__LERP(arguments.initial.backgroundColor.r, arguments.target.backgroundColor.r, lerpAmount), 4889 .g = CLAY__LERP(arguments.initial.backgroundColor.g, arguments.target.backgroundColor.g, lerpAmount), 4890 .b = CLAY__LERP(arguments.initial.backgroundColor.b, arguments.target.backgroundColor.b, lerpAmount), 4891 .a = CLAY__LERP(arguments.initial.backgroundColor.a, arguments.target.backgroundColor.a, lerpAmount), 4892 }; 4893 } 4894 if (arguments.properties & CLAY_TRANSITION_PROPERTY_OVERLAY_COLOR) { 4895 arguments.current->overlayColor = CLAY__INIT(Clay_Color) { 4896 .r = CLAY__LERP(arguments.initial.overlayColor.r, arguments.target.overlayColor.r, lerpAmount), 4897 .g = CLAY__LERP(arguments.initial.overlayColor.g, arguments.target.overlayColor.g, lerpAmount), 4898 .b = CLAY__LERP(arguments.initial.overlayColor.b, arguments.target.overlayColor.b, lerpAmount), 4899 .a = CLAY__LERP(arguments.initial.overlayColor.a, arguments.target.overlayColor.a, lerpAmount), 4900 }; 4901 } 4902 if (arguments.properties & CLAY_TRANSITION_PROPERTY_BORDER_COLOR) { 4903 arguments.current->borderColor = CLAY__INIT(Clay_Color) { 4904 .r = CLAY__LERP(arguments.initial.borderColor.r, arguments.target.borderColor.r, lerpAmount), 4905 .g = CLAY__LERP(arguments.initial.borderColor.g, arguments.target.borderColor.g, lerpAmount), 4906 .b = CLAY__LERP(arguments.initial.borderColor.b, arguments.target.borderColor.b, lerpAmount), 4907 .a = CLAY__LERP(arguments.initial.borderColor.a, arguments.target.borderColor.a, lerpAmount), 4908 }; 4909 } 4910 if (arguments.properties & CLAY_TRANSITION_PROPERTY_BORDER_WIDTH) { 4911 arguments.current->borderWidth = CLAY__INIT(Clay_BorderWidth) { 4912 .left = (uint16_t)CLAY__LERP(arguments.initial.borderWidth.left, arguments.target.borderWidth.left, lerpAmount), 4913 .right = (uint16_t)CLAY__LERP(arguments.initial.borderWidth.right, arguments.target.borderWidth.right, lerpAmount), 4914 .top = (uint16_t)CLAY__LERP(arguments.initial.borderWidth.top, arguments.target.borderWidth.top, lerpAmount), 4915 .bottom = (uint16_t)CLAY__LERP(arguments.initial.borderWidth.bottom, arguments.target.borderWidth.bottom, lerpAmount), 4916 .betweenChildren = (uint16_t)CLAY__LERP(arguments.initial.borderWidth.betweenChildren, arguments.target.borderWidth.betweenChildren, lerpAmount), 4917 }; 4918 } 4919 return ratio >= 1; 4920 } 4921 4922 #endif // CLAY_IMPLEMENTATION 4923 4924 /* 4925 LICENSE 4926 zlib/libpng license 4927 4928 Copyright (c) 2024 Nic Barker 4929 4930 This software is provided 'as-is', without any express or implied warranty. 4931 In no event will the authors be held liable for any damages arising from the 4932 use of this software. 4933 4934 Permission is granted to anyone to use this software for any purpose, 4935 including commercial applications, and to alter it and redistribute it 4936 freely, subject to the following restrictions: 4937 4938 1. The origin of this software must not be misrepresented; you must not 4939 claim that you wrote the original software. If you use this software in a 4940 product, an acknowledgment in the product documentation would be 4941 appreciated but is not required. 4942 4943 2. Altered source versions must be plainly marked as such, and must not 4944 be misrepresented as being the original software. 4945 4946 3. This notice may not be removed or altered from any source 4947 distribution. 4948 */