| 996 | } |
| 997 | node = next; |
| 998 | nodeTop = nextTop; |
| 999 | } |
| 1000 | return anchor; |
| 1001 | } |
| 1002 | |
| 1003 | ViewNode* preserveFindDescendantById(ViewNode* node, RawViewNodeId id, int depth) { |
| 1004 | // id 0 is the unset/default rawId (e.g. synthetic placeholder nodes); never "match" it, or |
| 1005 | // we would anchor to the first id-less node (content top) and jump the viewport. |
| 1006 | if (depth <= 0 || id == 0) |
| 1007 | return nullptr; |
| 1008 | for (auto* child : *node) { |
| 1009 | if (child->getRawId() == id) |
| 1010 | return child; |
| 1011 | auto* found = preserveFindDescendantById(child, id, depth - 1); |
| 1012 | if (found) |
| 1013 | return found; |
| 1014 | } |
| 1015 | return nullptr; |
| 1016 | } |
| 1017 | |
| 1018 | // Re-pick the anchor (the node at the viewport center) and remember its id + screen position |
| 1019 | // (absolute top relative to the viewport top) so a later content reflow can pin it back. Called |
| 1020 | // from both layout (updateScrollState) and scroll (handleOnScroll) so the anchor never goes stale |
| 1021 | // between layout passes. |
| 1022 | void refreshPreserveAnchor(ViewNode* scrollNode, ViewNodeScrollState& scrollState, float offsetY, float viewportH) { |
| 1023 | float probeY = offsetY + viewportH * 0.5f; |
| 1024 | // Only anchor on a node with a real, trackable rawId. id 0 (unset/default) can't be re-found |
| 1025 | // across passes, so don't record it -- clear instead and re-pick next pass. |
| 1026 | if (auto* anchor = preserveDescendantAtY(scrollNode, probeY); anchor != nullptr && anchor->getRawId() != 0) { |
| 1027 | scrollState.setPreserveAnchor(anchor->getRawId(), preserveAnchorAbsoluteTop(scrollNode, anchor) - offsetY); |
| 1028 | } else { |
| 1029 | scrollState.clearPreserveAnchor(); |
| 1030 | } |
| 1031 | } |
| 1032 | |
| 1033 | } // namespace |
| 1034 | |
| 1035 | void ViewNode::handleOnScroll(const Point& directionDependentContentOffset, |
nothing calls this directly
no test coverage detected