From 75338f73578e2efc4a31cc31797425447f4328ae Mon Sep 17 00:00:00 2001 From: Harrison Lambeth Date: Wed, 29 Jan 2025 19:03:50 -0700 Subject: [PATCH 1/4] Clay_GetElementIdsAtPoint --- clay.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/clay.h b/clay.h index cbc79be..e8a9639 100644 --- a/clay.h +++ b/clay.h @@ -764,6 +764,12 @@ typedef struct { void *userData; } Clay_ErrorHandler; +CLAY__TYPEDEF(Clay_PointQueryResult, struct +{ + int32_t length; + const Clay_ElementId *results; +}); + // Function Forward Declarations --------------------------------- // Public API functions ------------------------------------------ @@ -855,6 +861,7 @@ void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount); // Resets Clay's internal text measurement cache, useful if memory to represent strings is being re-used. // Similar behaviour can be achieved on an individual text element level by using Clay_TextElementConfig.hashStringContents void Clay_ResetMeasureTextCache(void); +Clay_PointQueryResult Clay_GetElementIdsAtPoint(Clay_Vector2 point); // Internal API functions required by macros ---------------------- @@ -1217,6 +1224,8 @@ struct Clay_Context { Clay__boolArray treeNodeVisited; Clay__charArray dynamicStringData; Clay__DebugElementDataArray debugElementData; + // Point querying + Clay__ElementIdArray pointQueryIds; }; Clay_Context* Clay__Context_Allocate_Arena(Clay_Arena *arena) { @@ -2008,6 +2017,7 @@ void Clay__InitializePersistentMemory(Clay_Context* context) { context->measureTextHashMap = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->measuredWords = Clay__MeasuredWordArray_Allocate_Arena(maxMeasureTextCacheWordCount, arena); context->pointerOverIds = Clay__ElementIdArray_Allocate_Arena(maxElementCount, arena); + context->pointQueryIds = Clay__ElementIdArray_Allocate_Arena(maxElementCount, arena); context->debugElementData = Clay__DebugElementDataArray_Allocate_Arena(maxElementCount, arena); context->arenaResetOffset = arena->nextAllocation; } @@ -3698,6 +3708,59 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { } } +CLAY_WASM_EXPORT("Clay_GetElementIdsAtPoint") +Clay_PointQueryResult Clay_GetElementIdsAtPoint(Clay_Vector2 position) { + Clay_Context* context = Clay_GetCurrentContext(); + if (context->booleanWarnings.maxElementsExceeded) { + return CLAY__INIT(Clay_PointQueryResult) { 0, NULL }; + } + context->pointQueryIds.length = 0; + Clay__int32_tArray dfsBuffer = context->layoutElementChildrenBuffer; + for (int32_t rootIndex = context->layoutElementTreeRoots.length - 1; rootIndex >= 0; --rootIndex) { + dfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&context->layoutElementTreeRoots, rootIndex); + Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); + context->treeNodeVisited.internalArray[0] = false; + bool found = false; + while (dfsBuffer.length > 0) { + if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + dfsBuffer.length--; + continue; + } + context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1)); + Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO think of a way around this, maybe the fact that it's essentially a binary tree limits the cost, but the worst case is not great + Clay_BoundingBox elementBox = mapItem->boundingBox; + elementBox.x -= root->pointerOffset.x; + elementBox.y -= root->pointerOffset.y; + if (mapItem) { + if ((Clay__PointIsInsideRect(position, elementBox))) { + Clay__ElementIdArray_Add(&context->pointQueryIds, mapItem->elementId); + found = true; + } + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + dfsBuffer.length--; + continue; + } + for (int32_t i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) { + Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]); + context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked + } + } else { + dfsBuffer.length--; + } + } + + Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, root->layoutElementIndex); + if (found && Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER) && + Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER).floatingElementConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_CAPTURE) { + break; + } + } + + return CLAY__INIT(Clay_PointQueryResult) { context->pointQueryIds.length, context->pointQueryIds.internalArray }; +} + CLAY_WASM_EXPORT("Clay_Initialize") Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler) { Clay_Context *context = Clay__Context_Allocate_Arena(&arena); From 1ef4d596010f1c2e214c2397ef468cf326cb923b Mon Sep 17 00:00:00 2001 From: Harrison Lambeth Date: Thu, 13 Feb 2025 23:45:32 -0700 Subject: [PATCH 2/4] Fix some things that broke after rebasing --- clay.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clay.h b/clay.h index e8a9639..2d09392 100644 --- a/clay.h +++ b/clay.h @@ -764,11 +764,11 @@ typedef struct { void *userData; } Clay_ErrorHandler; -CLAY__TYPEDEF(Clay_PointQueryResult, struct +typedef struct { int32_t length; const Clay_ElementId *results; -}); +} Clay_PointQueryResult; // Function Forward Declarations --------------------------------- @@ -3728,7 +3728,7 @@ Clay_PointQueryResult Clay_GetElementIdsAtPoint(Clay_Vector2 position) { continue; } context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; - Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1)); + Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1)); Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO think of a way around this, maybe the fact that it's essentially a binary tree limits the cost, but the worst case is not great Clay_BoundingBox elementBox = mapItem->boundingBox; elementBox.x -= root->pointerOffset.x; @@ -3752,8 +3752,8 @@ Clay_PointQueryResult Clay_GetElementIdsAtPoint(Clay_Vector2 position) { } Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&context->layoutElements, root->layoutElementIndex); - if (found && Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER) && - Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER).floatingElementConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_CAPTURE) { + if (found && Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING) && + Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).floatingElementConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_CAPTURE) { break; } } From ed4b43f73953551a232fcbc58247856d4e433420 Mon Sep 17 00:00:00 2001 From: Harrison Lambeth Date: Thu, 13 Feb 2025 23:56:37 -0700 Subject: [PATCH 3/4] Add some docs --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index b2482e8..2d112ad 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,7 @@ For help starting out or to discuss clay, considering joining [the discord serve - [Clay_PointerOver](#clay_pointerover) - [Clay_GetScrollContainerData](#clay_getscrollcontainerdata) - [Clay_GetElementId](#clay_getelementid) + - [Clay_GetElementIdsAtPoint](#clay_getelementidsatpoint) - [Element Macros](#element-macros) - [CLAY](#clay-1) - [CLAY_ID](#clay_id) @@ -197,6 +198,7 @@ For help starting out or to discuss clay, considering joining [the discord serve - [Clay_ScrollContainerData](#clay_scrollcontainerdata) - [Clay_ErrorHandler](#clay_errorhandler) - [Clay_ErrorData](#clay_errordata) + - [Clay_PointQueryResult](#clay_pointqueryresult) ## High Level Documentation @@ -728,6 +730,14 @@ Returns [Clay_ScrollContainerData](#clay_scrollcontainerdata) for the scroll con Returns a [Clay_ElementId](#clay_elementid) for the provided id string, used for querying element info such as mouseover state, scroll container data, etc. +### Clay_GetElementIdsAtPoint + +`Clay_PointQueryResult Clay_GetElementIdsAtPoint(Clay_Vector2 position)` + +Returns a [Clay_PointQueryResult](#clay_pointqueryresult) that contains a sorted stack of element ids at the specified position. This allows querying elements similar to [Clay_SetPointerState](#clay_setpointerstate), but without triggering hover functions or affecting hover states. + +> ⚠️ The returned Clay_PointQueryResult object becomes invalid the next time you call `Clay_GetElementIdsAtPoint`. If you need to call this multiple times in a frame, you will need to copy the data out of the Clay_PointQueryResult struct. + ## Element Macros ### CLAY() @@ -2129,3 +2139,27 @@ A [Clay_String](#clay_string) that provides a human readable description of the A generic pointer to extra userdata that is transparently passed through from `Clay_Initialize` to Clay's error handler callback. Defaults to NULL. --- + +### Clay_PointQueryResult + +```C +typedef struct +{ + int32t length; + const Clay_ElementId *results; +} Clay_PointQueryResult; +``` + +**Fields** + +**`.length`** - `int32_t` + +The number of element ids contained in `.results`. + +--- + +**`.results`** - `Clay_ElementId*` + +A pointer to a sorted array of `.length` [Clay_ElementIds](#clay_elementid), starting with the root element. + +--- From 81c19bae4f71a4db8941f98fd6b359b20e0becec Mon Sep 17 00:00:00 2001 From: Harrison Lambeth Date: Fri, 14 Feb 2025 00:18:58 -0700 Subject: [PATCH 4/4] Another doc update --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d112ad..84b8d39 100644 --- a/README.md +++ b/README.md @@ -736,7 +736,9 @@ Returns a [Clay_ElementId](#clay_elementid) for the provided id string, used for Returns a [Clay_PointQueryResult](#clay_pointqueryresult) that contains a sorted stack of element ids at the specified position. This allows querying elements similar to [Clay_SetPointerState](#clay_setpointerstate), but without triggering hover functions or affecting hover states. -> ⚠️ The returned Clay_PointQueryResult object becomes invalid the next time you call `Clay_GetElementIdsAtPoint`. If you need to call this multiple times in a frame, you will need to copy the data out of the Clay_PointQueryResult struct. +> ⚠️ This should not be called between BeginLayout and EndLayout, because layout data will be in flux. It is recommended to call this function before BeginLayout. + +> ⚠️ The returned Clay_PointQueryResult object becomes invalid the next time `Clay_GetElementIdsAtPoint` is called. If you need to call this multiple times in a frame, you must copy the data out of the Clay_PointQueryResult struct between calls. ## Element Macros