@@ -106,9 +106,16 @@ public class ClayContext : IDisposable
106106 public ClayList < int > OpenClipElementStack ;
107107 public ClayList < ScrollContainerDataInternal > ScrollContainerDatas ;
108108
109+ // IDs whose bounding box contains the pointer this frame.
110+ // Ordered topmost-first: highest-ZIndex root first, and within a root the
111+ // deepest last-child appears before its ancestors (postorder + forward-child push).
112+ public ClayList < ElementId > PointerOverIds ;
113+
109114 // Reusable temporary collections (avoid per-frame allocations)
110115 private readonly List < int > _bfsQueue = new ( 256 ) ;
111116 private readonly Dictionary < uint , int > _scrollContainerIndex = new ( ) ;
117+ private ClayList < int > _pointerDfsStack = new ( 256 ) ;
118+ private ClayList < bool > _pointerDfsVisited = new ( 256 ) ;
112119
113120 public ClayContext ( int maxElementCount = 8192 )
114121 {
@@ -140,6 +147,7 @@ public ClayContext(int maxElementCount = 8192)
140147 LayoutElementsHashMap = new ClayList < int > ( maxElementCount ) ;
141148 OpenClipElementStack = new ClayList < int > ( 64 ) ;
142149 ScrollContainerDatas = new ClayList < ScrollContainerDataInternal > ( 16 ) ;
150+ PointerOverIds = new ClayList < ElementId > ( maxElementCount ) ;
143151 }
144152
145153 /// <summary>
@@ -1442,6 +1450,88 @@ public void SetPointerState(Vector2 position, bool pressed)
14421450 ? PointerInteractionState . ReleasedThisFrame
14431451 : PointerInteractionState . Released ;
14441452 }
1453+
1454+ RebuildPointerOverIds ( position ) ;
1455+ }
1456+
1457+ /// <summary>
1458+ /// Rebuilds <see cref="PointerOverIds"/> for the current pointer position
1459+ /// without advancing the pointer state machine. Call after <see cref="EndLayout"/>
1460+ /// when hit-testing must run against the freshly computed layout.
1461+ /// </summary>
1462+ public void RefreshPointerOverIds ( ) => RebuildPointerOverIds ( PointerInfo . Position ) ;
1463+
1464+ private void RebuildPointerOverIds ( Vector2 position )
1465+ {
1466+ PointerOverIds . Clear ( ) ;
1467+ if ( LayoutElementTreeRoots . Length == 0 || LayoutElements . Length == 0 )
1468+ return ;
1469+
1470+ // Iterate roots topmost-first. EndLayout sorts roots ascending by ZIndex,
1471+ // so reverse iteration visits highest-ZIndex root first.
1472+ for ( int rootIndex = LayoutElementTreeRoots . Length - 1 ; rootIndex >= 0 ; rootIndex -- )
1473+ {
1474+ ref var root = ref LayoutElementTreeRoots [ rootIndex ] ;
1475+ var pointerOffset = root . PointerOffset ;
1476+ var adjustedPos = new Vector2 ( position . X + pointerOffset . X , position . Y + pointerOffset . Y ) ;
1477+
1478+ _pointerDfsStack . Clear ( ) ;
1479+ _pointerDfsVisited . Clear ( ) ;
1480+ _pointerDfsStack . Add ( root . LayoutElementIndex ) ;
1481+ _pointerDfsVisited . Add ( false ) ;
1482+
1483+ bool found = false ;
1484+ while ( _pointerDfsStack . Length > 0 )
1485+ {
1486+ int depth = _pointerDfsStack . Length - 1 ;
1487+ int elementIndex = _pointerDfsStack [ depth ] ;
1488+
1489+ if ( _pointerDfsVisited [ depth ] )
1490+ {
1491+ // Post-order add: children already processed.
1492+ int hashIdx = GetHashMapItemIndex ( LayoutElements [ elementIndex ] . Id ) ;
1493+ if ( hashIdx >= 0 )
1494+ {
1495+ ref var item = ref LayoutElementsHashMapInternal [ hashIdx ] ;
1496+ if ( item . BoundingBox . Contains ( adjustedPos ) )
1497+ {
1498+ PointerOverIds . Add ( item . ElementId ) ;
1499+ found = true ;
1500+ }
1501+ }
1502+ _pointerDfsStack . Length -- ;
1503+ _pointerDfsVisited . Length -- ;
1504+ continue ;
1505+ }
1506+
1507+ _pointerDfsVisited [ depth ] = true ;
1508+ ref var element = ref LayoutElements [ elementIndex ] ;
1509+
1510+ // Text leaves: no children to descend into.
1511+ if ( HasConfig ( ref element , ElementConfigType . Text ) )
1512+ continue ;
1513+
1514+ // Push children forward so last-child pops first (topmost subtree first).
1515+ int childStart = element . Children . StartIndex ;
1516+ int childLen = element . Children . Length ;
1517+ for ( int i = 0 ; i < childLen ; i ++ )
1518+ {
1519+ _pointerDfsStack . Add ( LayoutElementChildren [ childStart + i ] ) ;
1520+ _pointerDfsVisited . Add ( false ) ;
1521+ }
1522+ }
1523+
1524+ // Floating root with Capture mode swallows pointer events: don't continue
1525+ // into roots underneath it.
1526+ if ( ! found ) continue ;
1527+ ref var rootElement = ref LayoutElements [ root . LayoutElementIndex ] ;
1528+ int floatingIdx = FindConfigIndex ( ref rootElement , ElementConfigType . Floating ) ;
1529+ if ( floatingIdx >= 0 &&
1530+ FloatingElementConfigs [ floatingIdx ] . PointerCaptureMode == PointerCaptureMode . Capture )
1531+ {
1532+ break ;
1533+ }
1534+ }
14451535 }
14461536
14471537 public void SetLayoutDimensions ( Dimensions dimensions )
0 commit comments