diff --git a/clay.h b/clay.h index 8436831..377e8fb 100644 --- a/clay.h +++ b/clay.h @@ -785,51 +785,41 @@ typedef struct uint32_t elementId; bool openThisFrame; bool pointerScrollActive; -} Clay__ScrollContainerData; +} Clay__ScrollContainerDataInternal; -Clay__ScrollContainerData CLAY__SCROLL_CONTAINER_DEFAULT = (Clay__ScrollContainerData) {}; +Clay__ScrollContainerDataInternal CLAY__SCROLL_CONTAINER_DEFAULT = (Clay__ScrollContainerDataInternal) {}; -// __GENERATED__ template array_define TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray +// __GENERATED__ template define,array_add,array_get TYPE=Clay__ScrollContainerDataInternal NAME=Clay__ScrollContainerDataInternalArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_DEFAULT #pragma region generated typedef struct { uint32_t capacity; uint32_t length; - Clay__ScrollContainerData *internalArray; -} Clay__ScrollContainerDataArray; + Clay__ScrollContainerDataInternal *internalArray; +} Clay__ScrollContainerDataInternalArray; -Clay__ScrollContainerDataArray Clay__ScrollContainerDataArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { - return (Clay__ScrollContainerDataArray){.capacity = capacity, .length = 0, .internalArray = (Clay__ScrollContainerData *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__ScrollContainerData), CLAY__ALIGNMENT(Clay__ScrollContainerData), arena)}; +Clay__ScrollContainerDataInternalArray Clay__ScrollContainerDataInternalArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return (Clay__ScrollContainerDataInternalArray){.capacity = capacity, .length = 0, .internalArray = (Clay__ScrollContainerDataInternal *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__ScrollContainerDataInternal), CLAY__ALIGNMENT(Clay__ScrollContainerDataInternal), arena)}; } -#pragma endregion -// __GENERATED__ template - -// __GENERATED__ template array_add TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_DEFAULT -#pragma region generated -Clay__ScrollContainerData *Clay__ScrollContainerDataArray_Add(Clay__ScrollContainerDataArray *array, Clay__ScrollContainerData item) { +Clay__ScrollContainerDataInternal *Clay__ScrollContainerDataInternalArray_Add(Clay__ScrollContainerDataInternalArray *array, Clay__ScrollContainerDataInternal item) { if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { array->internalArray[array->length++] = item; return &array->internalArray[array->length - 1]; } return &CLAY__SCROLL_CONTAINER_DEFAULT; } -#pragma endregion -// __GENERATED__ template - -// __GENERATED__ template array_get TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_DEFAULT -#pragma region generated -Clay__ScrollContainerData *Clay__ScrollContainerDataArray_Get(Clay__ScrollContainerDataArray *array, int index) { +Clay__ScrollContainerDataInternal *Clay__ScrollContainerDataInternalArray_Get(Clay__ScrollContainerDataInternalArray *array, int index) { return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__SCROLL_CONTAINER_DEFAULT; } #pragma endregion // __GENERATED__ template -// __GENERATED__ template array_remove_swapback TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray DEFAULT_VALUE=CLAY__SCROLL_CONTAINER_DEFAULT +// __GENERATED__ template array_remove_swapback TYPE=Clay__ScrollContainerDataInternal NAME=Clay__ScrollContainerDataInternalArray DEFAULT_VALUE=CLAY__SCROLL_CONTAINER_DEFAULT #pragma region generated -Clay__ScrollContainerData Clay__ScrollContainerDataArray_RemoveSwapback(Clay__ScrollContainerDataArray *array, int index) { +Clay__ScrollContainerDataInternal Clay__ScrollContainerDataInternalArray_RemoveSwapback(Clay__ScrollContainerDataInternalArray *array, int index) { if (Clay__Array_RangeCheck(index, array->length)) { array->length--; - Clay__ScrollContainerData removed = array->internalArray[index]; + Clay__ScrollContainerDataInternal removed = array->internalArray[index]; array->internalArray[index] = array->internalArray[array->length]; return removed; } @@ -1068,7 +1058,7 @@ Clay__MeasureTextCacheItemArray Clay__measureTextHashMapInternal; Clay__int32_tArray Clay__measureTextHashMap; Clay__int32_tArray Clay__openClipElementStack; Clay__int32_tArray Clay__pointerOverIds; -Clay__ScrollContainerDataArray Clay__scrollContainerOffsets; +Clay__ScrollContainerDataInternalArray Clay__scrollContainerDatas; Clay__BoolArray Clay__treeNodeVisited; #if CLAY_WASM @@ -1272,9 +1262,9 @@ void Clay__OpenCustomElement(uint32_t id, Clay_LayoutConfig *layoutConfig, Clay_ Clay_LayoutElement *Clay__OpenScrollElement(uint32_t id, Clay_LayoutConfig *layoutConfig, Clay_ScrollContainerElementConfig *scrollConfig) { Clay_LayoutElement *scrollElement = Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER, layoutConfig, (Clay_ElementConfigUnion){ .scrollElementConfig = scrollConfig }); Clay__int32_tArray_Add(&Clay__openClipElementStack, (int)scrollElement->id); - Clay__ScrollContainerData *scrollOffset = CLAY__NULL; - for (int i = 0; i < Clay__scrollContainerOffsets.length; i++) { - Clay__ScrollContainerData *mapping = Clay__ScrollContainerDataArray_Get(&Clay__scrollContainerOffsets, i); + Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL; + for (int i = 0; i < Clay__scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); if (id == mapping->elementId) { scrollOffset = mapping; scrollOffset->layoutElement = scrollElement; @@ -1282,7 +1272,7 @@ Clay_LayoutElement *Clay__OpenScrollElement(uint32_t id, Clay_LayoutConfig *layo } } if (!scrollOffset) { - Clay__ScrollContainerDataArray_Add(&Clay__scrollContainerOffsets, (Clay__ScrollContainerData){.elementId = id, .layoutElement = scrollElement, .scrollOrigin = {-1,-1}, .openThisFrame = true}); + Clay__ScrollContainerDataInternalArray_Add(&Clay__scrollContainerDatas, (Clay__ScrollContainerDataInternal){.elementId = id, .layoutElement = scrollElement, .scrollOrigin = {-1,-1}, .openThisFrame = true}); } return scrollElement; } @@ -1427,7 +1417,7 @@ void Clay__InitializeEphemeralMemory(Clay_Arena *arena) { } void Clay__InitializePersistentMemory(Clay_Arena *arena) { - Clay__scrollContainerOffsets = Clay__ScrollContainerDataArray_Allocate_Arena(10, arena); + Clay__scrollContainerDatas = Clay__ScrollContainerDataInternalArray_Allocate_Arena(10, arena); Clay__layoutElementsHashMapInternal = Clay__LayoutElementHashMapItemArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); Clay__layoutElementsHashMap = Clay__int32_tArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); Clay__measureTextHashMapInternal = Clay__MeasureTextCacheItemArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena); @@ -1504,6 +1494,21 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex); Clay_LayoutElement *rootElement = root->layoutElement; Clay__LayoutElementPointerArray_Add(&bfsBuffer, root->layoutElement); + + // Size floating containers to their parents + if (rootElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER) { + Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(rootElement->elementConfig.floatingElementConfig->parentId); + if (parentItem) { + Clay_LayoutElement *parentLayoutElement = parentItem->layoutElement; + if (rootElement->layoutConfig->sizing.width.type == CLAY__SIZING_TYPE_GROW) { + rootElement->dimensions.width = parentLayoutElement->dimensions.width - (float)parentLayoutElement->layoutConfig->padding.x * 2; + } + if (rootElement->layoutConfig->sizing.height.type == CLAY__SIZING_TYPE_GROW) { + rootElement->dimensions.height = parentLayoutElement->dimensions.height - (float)parentLayoutElement->layoutConfig->padding.x * 2; + } + } + } + rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->layoutConfig->sizing.width.sizeMinMax.min), rootElement->layoutConfig->sizing.width.sizeMinMax.max); rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->layoutConfig->sizing.height.sizeMinMax.min), rootElement->layoutConfig->sizing.height.sizeMinMax.max); @@ -1609,13 +1614,6 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { } void Clay__CalculateFinalLayout(int screenWidth, int screenHeight) { - // layoutElementsHashMap has non-linear access pattern so just resetting .length won't zero out the data. - // Need to zero it all out here - for (int i = 0; i < Clay__layoutElementsHashMap.capacity; ++i) { - Clay__layoutElementsHashMap.internalArray[i] = -1; - } - Clay__layoutElementsHashMapInternal.length = 0; - // Calculate sizing along the X axis Clay__SizeContainersAlongAxis(true); @@ -1759,6 +1757,13 @@ void Clay__CalculateFinalLayout(int screenWidth, int screenHeight) { // Calculate sizing along the Y axis Clay__SizeContainersAlongAxis(false); + // layoutElementsHashMap has non-linear access pattern so just resetting .length won't zero out the data. + // Need to zero it all out here + for (int i = 0; i < Clay__layoutElementsHashMap.capacity; ++i) { + Clay__layoutElementsHashMap.internalArray[i] = -1; + } + Clay__layoutElementsHashMapInternal.length = 0; + // Calculate final positions and generate render commands Clay__renderCommands.length = 0; dfsBuffer.length = 0; @@ -1855,7 +1860,7 @@ void Clay__CalculateFinalLayout(int screenWidth, int screenHeight) { currentElementBoundingBox.height += expand.height * 2; } - Clay__ScrollContainerData *scrollContainerData = CLAY__NULL; + Clay__ScrollContainerDataInternal *scrollContainerData = CLAY__NULL; // Apply scroll offsets to container if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER) { Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) { @@ -1865,8 +1870,8 @@ void Clay__CalculateFinalLayout(int screenWidth, int screenHeight) { }); // This linear scan could theoretically be slow under very strange conditions, but I can't imagine a real UI with more than a few 10's of scroll containers - for (int i = 0; i < Clay__scrollContainerOffsets.length; i++) { - Clay__ScrollContainerData *mapping = Clay__ScrollContainerDataArray_Get(&Clay__scrollContainerOffsets, i); + for (int i = 0; i < Clay__scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); if (mapping->layoutElement == currentElement) { scrollContainerData = mapping; mapping->boundingBox = currentElementBoundingBox; @@ -2144,18 +2149,18 @@ CLAY_WASM_EXPORT("Clay_UpdateScrollContainers") void Clay_UpdateScrollContainers(bool isPointerActive, Clay_Vector2 scrollDelta, float deltaTime) { // Don't apply scroll events to ancestors of the inner element int32_t highestPriorityElementIndex = -1; - Clay__ScrollContainerData *highestPriorityScrollData = CLAY__NULL; - for (int i = 0; i < Clay__scrollContainerOffsets.length; i++) { - Clay__ScrollContainerData *scrollData = Clay__ScrollContainerDataArray_Get(&Clay__scrollContainerOffsets, i); + Clay__ScrollContainerDataInternal *highestPriorityScrollData = CLAY__NULL; + for (int i = 0; i < Clay__scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *scrollData = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); if (!scrollData->openThisFrame) { - Clay__ScrollContainerDataArray_RemoveSwapback(&Clay__scrollContainerOffsets, i); + Clay__ScrollContainerDataInternalArray_RemoveSwapback(&Clay__scrollContainerDatas, i); continue; } scrollData->openThisFrame = false; Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(scrollData->elementId); // Element isn't rendered this frame but scroll offset has been retained if (!hashMapItem) { - Clay__ScrollContainerDataArray_RemoveSwapback(&Clay__scrollContainerOffsets, i); + Clay__ScrollContainerDataInternalArray_RemoveSwapback(&Clay__scrollContainerDatas, i); continue; } @@ -2279,6 +2284,35 @@ bool Clay_PointerOver(uint32_t id) { // TODO return priority for separating mult return false; } +typedef struct +{ + // Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. + // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling. + Clay_Vector2 *scrollPosition; + Clay_Dimensions scrollContainerDimensions; + Clay_Dimensions contentDimensions; + Clay_ScrollContainerElementConfig config; + // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. + bool found; +} Clay_ScrollContainerData; + +CLAY_WASM_EXPORT("Clay_GetScrollContainerData") +Clay_ScrollContainerData Clay_GetScrollContainerData(uint32_t id) { + for (int i = 0; i < Clay__scrollContainerDatas.length; ++i) { + Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); + if (scrollContainerData->elementId == id) { + return (Clay_ScrollContainerData) { + .scrollPosition = &scrollContainerData->scrollPosition, + .scrollContainerDimensions = (Clay_Dimensions) { scrollContainerData->boundingBox.width, scrollContainerData->boundingBox.height }, + .contentDimensions = scrollContainerData->contentSize, + .config = *scrollContainerData->layoutElement->elementConfig.scrollElementConfig, + .found = true + }; + } + } + return (Clay_ScrollContainerData){}; +} + #endif //CLAY_IMPLEMENTATION /* diff --git a/examples/raylib-sidebar-scrolling-container/main.c b/examples/raylib-sidebar-scrolling-container/main.c index 9ac1349..d87d0bd 100644 --- a/examples/raylib-sidebar-scrolling-container/main.c +++ b/examples/raylib-sidebar-scrolling-container/main.c @@ -93,7 +93,7 @@ Clay_RenderCommandArray CreateLayout() { }); }); }); -// + CLAY_FLOATING_CONTAINER(CLAY_ID("Blob4Floating"), &CLAY_LAYOUT_DEFAULT, CLAY_FLOATING_CONFIG(.zIndex = 1, .parentId = CLAY_ID("SidebarBlob4")), { CLAY_SCROLL_CONTAINER(CLAY_ID("ScrollContainer"), CLAY_LAYOUT(.sizing = { .height = CLAY_SIZING_FIXED(200) }, .childGap = 2), CLAY_SCROLL_CONFIG(.vertical = true), { CLAY_FLOATING_CONTAINER(CLAY_ID("FloatingContainer"), CLAY_LAYOUT(), CLAY_FLOATING_CONFIG(.zIndex = 1), { @@ -108,11 +108,22 @@ Clay_RenderCommandArray CreateLayout() { }); }); }); + Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(CLAY_ID("MainContent")); + CLAY_FLOATING_CONTAINER(CLAY_ID("ScrollBar"), &CLAY_LAYOUT_DEFAULT, CLAY_FLOATING_CONFIG(.offset = { .y = -(scrollData.scrollPosition->y / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height }, .zIndex = 1, .parentId = CLAY_ID("MainContent"), .attachment = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP}), { + CLAY_RECTANGLE(CLAY_ID("ScrollBarButton"), CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(12), CLAY_SIZING_FIXED((scrollData.scrollContainerDimensions.height / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height)}), CLAY_RECTANGLE_CONFIG(.cornerRadius = 6, .color = Clay_PointerOver(CLAY_ID("ScrollBar")) ? (Clay_Color){100, 100, 140, 150} : (Clay_Color){120, 120, 160, 150}), {}); + }); }); return Clay_EndLayout(GetScreenWidth(), GetScreenHeight()); } -int display_size_changed = 0; +typedef struct +{ + Clay_Vector2 clickOrigin; + Clay_Vector2 positionOrigin; + bool mouseDown; +} ScrollbarData; + +ScrollbarData scrollbarData = (ScrollbarData) {}; void UpdateDrawFrame(void) { @@ -122,8 +133,34 @@ void UpdateDrawFrame(void) mouseWheelY = mouseWheelDelta.y; //---------------------------------------------------------------------------------- // Handle scroll containers - Clay_SetPointerPosition(RAYLIB_VECTOR2_TO_CLAY_VECTOR2(GetMousePosition())); - Clay_UpdateScrollContainers(IsMouseButtonDown(0), (Clay_Vector2) {mouseWheelX, mouseWheelY}, GetFrameTime()); + Clay_Vector2 mousePosition = RAYLIB_VECTOR2_TO_CLAY_VECTOR2(GetMousePosition()); + Clay_SetPointerPosition(mousePosition); + if (!IsMouseButtonDown(0)) { + scrollbarData.mouseDown = false; + } + + if (IsMouseButtonDown(0) && !scrollbarData.mouseDown && Clay_PointerOver(CLAY_ID("ScrollBar"))) { + Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(CLAY_ID("MainContent")); + scrollbarData.clickOrigin = mousePosition; + scrollbarData.positionOrigin = *scrollContainerData.scrollPosition; + scrollbarData.mouseDown = true; + } else if (scrollbarData.mouseDown) { + Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(CLAY_ID("MainContent")); + if (scrollContainerData.contentDimensions.height > 0) { + Clay_Vector2 ratio = (Clay_Vector2) { + scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width, + scrollContainerData.contentDimensions.height / scrollContainerData.scrollContainerDimensions.height, + }; + if (scrollContainerData.config.vertical) { + scrollContainerData.scrollPosition->y = scrollbarData.positionOrigin.y + (scrollbarData.clickOrigin.y - mousePosition.y) * ratio.y; + } + if (scrollContainerData.config.horizontal) { + scrollContainerData.scrollPosition->x = scrollbarData.positionOrigin.x + (scrollbarData.clickOrigin.x - mousePosition.x) * ratio.x; + } + } + } + + Clay_UpdateScrollContainers(false, (Clay_Vector2) {mouseWheelX, mouseWheelY}, GetFrameTime()); // Generate the auto layout for rendering double currentTime = GetTime(); Clay_RenderCommandArray renderCommands = CreateLayout();