From 75338f73578e2efc4a31cc31797425447f4328ae Mon Sep 17 00:00:00 2001
From: Harrison Lambeth <harrison@nau.edu>
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 <harrison@nau.edu>
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 <harrison@nau.edu>
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 <harrison@nau.edu>
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