Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Clay_GetElementIdsAtPoint #241

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -728,6 +730,16 @@ 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.

> ⚠️ 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

### CLAY()
Expand Down Expand Up @@ -2129,3 +2141,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.

---
63 changes: 63 additions & 0 deletions clay.h
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,12 @@ typedef struct {
void *userData;
} Clay_ErrorHandler;

typedef struct
{
int32_t length;
const Clay_ElementId *results;
} Clay_PointQueryResult;

// Function Forward Declarations ---------------------------------

// Public API functions ------------------------------------------
Expand Down Expand Up @@ -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 ----------------------

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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_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;
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) &&
Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING).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);
Expand Down
Loading