Fix UI document host input integration build

This commit is contained in:
2026-04-05 21:52:30 +08:00
parent 578f5dd99c
commit dc0e8b938f
2 changed files with 609 additions and 75 deletions

View File

@@ -1,5 +1,6 @@
#pragma once
#include <XCEngine/UI/Input/UIInputDispatcher.h>
#include <XCEngine/UI/Runtime/UIScreenTypes.h>
#include <cstdint>
@@ -22,6 +23,20 @@ public:
class UIDocumentScreenHost final : public IUIScreenDocumentHost {
public:
struct InputDebugSnapshot {
std::uint64_t totalPointerEventCount = 0u;
UIPoint pointerPosition = {};
bool pointerInsideViewport = false;
std::string hoveredStateKey = {};
std::string focusedStateKey = {};
std::string activeStateKey = {};
std::string captureStateKey = {};
std::string lastEventType = {};
std::string lastTargetStateKey = {};
std::string lastTargetKind = {};
std::string lastResult = {};
};
struct ScrollDebugSnapshot {
std::uint64_t totalWheelEventCount = 0u;
std::uint64_t handledWheelEventCount = 0u;
@@ -42,10 +57,20 @@ public:
UIScreenFrameResult BuildFrame(
const UIScreenDocument& document,
const UIScreenFrameInput& input) override;
const InputDebugSnapshot& GetInputDebugSnapshot() const;
const ScrollDebugSnapshot& GetScrollDebugSnapshot() const;
private:
struct PointerState {
UIPoint position = {};
bool hasPosition = false;
bool insideViewport = false;
};
UIInputDispatcher m_inputDispatcher;
std::unordered_map<std::string, float> m_verticalScrollOffsets = {};
PointerState m_pointerState = {};
InputDebugSnapshot m_inputDebugSnapshot = {};
ScrollDebugSnapshot m_scrollDebugSnapshot = {};
};

View File

@@ -41,6 +41,8 @@ constexpr float kHeaderTextGap = 2.0f;
struct RuntimeLayoutNode {
const UIDocumentNode* source = nullptr;
std::string stateKey = {};
UIElementId elementId = 0u;
UIInputPath inputPath = {};
std::vector<RuntimeLayoutNode> children = {};
UISize desiredSize = {};
UISize minimumSize = {};
@@ -48,9 +50,19 @@ struct RuntimeLayoutNode {
UIRect rect = {};
UIRect scrollViewportRect = {};
float scrollOffsetY = 0.0f;
bool pointerInteractive = false;
bool focusable = false;
bool wantsPointerCapture = false;
bool isScrollView = false;
};
struct RuntimeNodeVisualState {
bool hovered = false;
bool focused = false;
bool active = false;
bool capture = false;
};
UIColor ToUIColor(const Color& color) {
return UIColor(color.r, color.g, color.b, color.a);
}
@@ -59,6 +71,48 @@ std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
std::string ToLowerAscii(std::string value) {
std::transform(
value.begin(),
value.end(),
value.begin(),
[](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
return value;
}
bool TryParseBoolString(const std::string& text, bool& outValue) {
if (text.empty()) {
return false;
}
const std::string normalized = ToLowerAscii(text);
if (normalized == "true" || normalized == "1" || normalized == "yes" || normalized == "on") {
outValue = true;
return true;
}
if (normalized == "false" || normalized == "0" || normalized == "no" || normalized == "off") {
outValue = false;
return true;
}
return false;
}
UIElementId HashStateKeyToElementId(const std::string& stateKey) {
constexpr UIElementId kOffsetBasis = 14695981039346656037ull;
constexpr UIElementId kPrime = 1099511628211ull;
UIElementId hash = kOffsetBasis;
for (unsigned char value : stateKey) {
hash ^= static_cast<UIElementId>(value);
hash *= kPrime;
}
return hash == 0u ? 1u : hash;
}
bool IsUtf8ContinuationByte(unsigned char value) {
return (value & 0xC0u) == 0x80u;
}
@@ -103,6 +157,14 @@ std::string GetAttribute(
return attribute != nullptr ? ToStdString(attribute->value) : fallback;
}
bool ParseBoolAttribute(
const UIDocumentNode& node,
const char* name,
bool fallback) {
bool value = fallback;
return TryParseBoolString(GetAttribute(node, name), value) ? value : fallback;
}
float MeasureHeaderTextWidth(const UIDocumentNode& node) {
float width = 0.0f;
@@ -235,6 +297,10 @@ bool IsScrollViewTag(const std::string& tagName) {
return tagName == "ScrollView";
}
bool IsButtonTag(const std::string& tagName) {
return tagName == "Button";
}
bool IsContainerTag(const UIDocumentNode& node) {
if (node.children.Size() > 0u) {
return true;
@@ -249,6 +315,24 @@ bool IsContainerTag(const UIDocumentNode& node) {
tagName == "Button";
}
bool IsPointerInteractiveNode(const UIDocumentNode& node) {
const std::string tagName = ToStdString(node.tagName);
return ParseBoolAttribute(node, "interactive", IsButtonTag(tagName) || IsScrollViewTag(tagName));
}
bool IsFocusableNode(const UIDocumentNode& node) {
const std::string tagName = ToStdString(node.tagName);
return ParseBoolAttribute(node, "focusable", IsButtonTag(tagName));
}
bool WantsPointerCapture(const UIDocumentNode& node) {
if (ParseBoolAttribute(node, "capturePointer", false)) {
return true;
}
return ToLowerAscii(GetAttribute(node, "capture")) == "pointer";
}
std::string BuildNodeStateKeySegment(
const UIDocumentNode& source,
std::size_t siblingIndex) {
@@ -280,7 +364,137 @@ bool RectContainsPoint(const UIRect& rect, const UIPoint& point) {
point.y <= rect.y + rect.height;
}
Color ResolveBackgroundColor(const UIDocumentNode& node) {
bool HasPositiveArea(const UIRect& rect) {
return rect.width > 0.0f && rect.height > 0.0f;
}
UIRect IntersectRects(const UIRect& lhs, const UIRect& rhs) {
const float left = (std::max)(lhs.x, rhs.x);
const float top = (std::max)(lhs.y, rhs.y);
const float right = (std::min)(lhs.x + lhs.width, rhs.x + rhs.width);
const float bottom = (std::min)(lhs.y + lhs.height, rhs.y + rhs.height);
return UIRect(
left,
top,
(std::max)(0.0f, right - left),
(std::max)(0.0f, bottom - top));
}
const UIRect& GetNodeInteractionRect(const RuntimeLayoutNode& node) {
return node.isScrollView ? node.scrollViewportRect : node.rect;
}
bool IsNodeTargetable(const RuntimeLayoutNode& node) {
return node.pointerInteractive || node.focusable || node.isScrollView;
}
const RuntimeLayoutNode* FindNodeByElementId(
const RuntimeLayoutNode& node,
UIElementId elementId) {
if (node.elementId == elementId) {
return &node;
}
for (const RuntimeLayoutNode& child : node.children) {
if (const RuntimeLayoutNode* found = FindNodeByElementId(child, elementId); found != nullptr) {
return found;
}
}
return nullptr;
}
RuntimeLayoutNode* FindNodeByElementId(
RuntimeLayoutNode& node,
UIElementId elementId) {
if (node.elementId == elementId) {
return &node;
}
for (RuntimeLayoutNode& child : node.children) {
if (RuntimeLayoutNode* found = FindNodeByElementId(child, elementId); found != nullptr) {
return found;
}
}
return nullptr;
}
bool PathTargetExists(
const RuntimeLayoutNode& root,
const UIInputPath& path) {
return !path.Empty() && FindNodeByElementId(root, path.Target()) != nullptr;
}
std::string ResolveStateKeyForPathTarget(
const RuntimeLayoutNode& root,
const UIInputPath& path) {
if (path.Empty()) {
return {};
}
if (const RuntimeLayoutNode* node = FindNodeByElementId(root, path.Target()); node != nullptr) {
return node->stateKey;
}
return {};
}
const RuntimeLayoutNode* FindDeepestInputTarget(
const RuntimeLayoutNode& node,
const UIPoint& point,
const UIRect* clipRect = nullptr) {
if (clipRect != nullptr && (!HasPositiveArea(*clipRect) || !RectContainsPoint(*clipRect, point))) {
return nullptr;
}
UIRect nextClip = clipRect != nullptr ? *clipRect : node.rect;
if (!HasPositiveArea(nextClip)) {
nextClip = node.rect;
}
if (node.isScrollView) {
nextClip = clipRect != nullptr
? IntersectRects(*clipRect, node.scrollViewportRect)
: node.scrollViewportRect;
}
for (const RuntimeLayoutNode& child : node.children) {
if (const RuntimeLayoutNode* found = FindDeepestInputTarget(child, point, &nextClip); found != nullptr) {
return found;
}
}
const UIRect& targetRect = GetNodeInteractionRect(node);
if (!IsNodeTargetable(node) || !HasPositiveArea(targetRect)) {
return nullptr;
}
if (clipRect != nullptr && !RectContainsPoint(*clipRect, point)) {
return nullptr;
}
return RectContainsPoint(targetRect, point) ? &node : nullptr;
}
UIInputPath ResolveHoveredPath(
const RuntimeLayoutNode& root,
const UIPoint& pointerPosition,
bool hasPointerPosition,
bool pointerInsideViewport) {
if (!hasPointerPosition || !pointerInsideViewport) {
return {};
}
if (const RuntimeLayoutNode* hovered = FindDeepestInputTarget(root, pointerPosition); hovered != nullptr) {
return hovered->inputPath;
}
return {};
}
Color ResolveBackgroundColor(
const UIDocumentNode& node,
const RuntimeNodeVisualState& state) {
const std::string tone = GetAttribute(node, "tone");
const std::string tagName = ToStdString(node.tagName);
@@ -294,13 +508,33 @@ Color ResolveBackgroundColor(const UIDocumentNode& node) {
return Color(0.22f, 0.22f, 0.22f, 1.0f);
}
if (tagName == "Button") {
if (state.active || state.capture) {
return Color(0.30f, 0.30f, 0.30f, 1.0f);
}
if (state.hovered) {
return Color(0.27f, 0.27f, 0.27f, 1.0f);
}
return Color(0.24f, 0.24f, 0.24f, 1.0f);
}
return Color(0.16f, 0.16f, 0.16f, 1.0f);
}
Color ResolveBorderColor(const UIDocumentNode& node) {
Color ResolveBorderColor(
const UIDocumentNode& node,
const RuntimeNodeVisualState& state) {
if (state.capture) {
return Color(0.82f, 0.82f, 0.82f, 1.0f);
}
if (state.focused || state.active) {
return Color(0.62f, 0.62f, 0.62f, 1.0f);
}
if (state.hovered) {
return Color(0.45f, 0.45f, 0.45f, 1.0f);
}
const std::string tone = GetAttribute(node, "tone");
if (tone == "accent") {
return Color(0.42f, 0.42f, 0.42f, 1.0f);
@@ -312,17 +546,46 @@ Color ResolveBorderColor(const UIDocumentNode& node) {
return Color(0.30f, 0.30f, 0.30f, 1.0f);
}
float ResolveBorderThickness(const RuntimeNodeVisualState& state) {
return (state.focused || state.active || state.capture) ? 2.0f : 1.0f;
}
bool IsPathTarget(
const UIInputPath& path,
UIElementId elementId) {
return !path.Empty() && path.Target() == elementId;
}
RuntimeNodeVisualState ResolveNodeVisualState(
const RuntimeLayoutNode& node,
const UIInputPath& hoveredPath,
const UIFocusController& focusController) {
RuntimeNodeVisualState state = {};
state.hovered = IsPathTarget(hoveredPath, node.elementId);
state.focused = IsPathTarget(focusController.GetFocusedPath(), node.elementId);
state.active = IsPathTarget(focusController.GetActivePath(), node.elementId);
state.capture = IsPathTarget(focusController.GetPointerCapturePath(), node.elementId);
return state;
}
RuntimeLayoutNode BuildLayoutTree(
const UIDocumentNode& source,
const std::string& parentStateKey,
const UIInputPath& parentInputPath,
std::size_t siblingIndex) {
RuntimeLayoutNode node = {};
node.source = &source;
node.stateKey = parentStateKey + "/" + BuildNodeStateKeySegment(source, siblingIndex);
node.elementId = HashStateKeyToElementId(node.stateKey);
node.inputPath = parentInputPath;
node.inputPath.elements.push_back(node.elementId);
node.pointerInteractive = IsPointerInteractiveNode(source);
node.focusable = ParseBoolAttribute(source, "focusable", IsFocusableNode(source));
node.wantsPointerCapture = WantsPointerCapture(source);
node.isScrollView = IsScrollViewTag(ToStdString(source.tagName));
node.children.reserve(source.children.Size());
for (std::size_t index = 0; index < source.children.Size(); ++index) {
node.children.push_back(BuildLayoutTree(source.children[index], node.stateKey, index));
node.children.push_back(BuildLayoutTree(source.children[index], node.stateKey, node.inputPath, index));
}
return node;
}
@@ -534,16 +797,13 @@ const RuntimeLayoutNode* FindFirstScrollView(const RuntimeLayoutNode& node) {
return nullptr;
}
bool ApplyScrollWheelInput(
bool ApplyScrollWheelEvent(
RuntimeLayoutNode& root,
const UIScreenFrameInput& input,
const UIInputEvent& event,
std::unordered_map<std::string, float>& verticalScrollOffsets,
UIDocumentScreenHost::ScrollDebugSnapshot& scrollDebugSnapshot) {
bool changed = false;
for (const UIInputEvent& event : input.events) {
if (event.type != UIInputEventType::PointerWheel) {
continue;
return false;
}
++scrollDebugSnapshot.totalWheelEventCount;
@@ -558,7 +818,7 @@ bool ApplyScrollWheelInput(
RuntimeLayoutNode* hoveredScrollView = FindDeepestHoveredScrollView(root, event.position);
if (hoveredScrollView == nullptr) {
continue;
return false;
}
scrollDebugSnapshot.lastTargetStateKey = hoveredScrollView->stateKey;
@@ -569,13 +829,13 @@ bool ApplyScrollWheelInput(
if (scrollDebugSnapshot.lastOverflow <= 0.0f) {
scrollDebugSnapshot.lastResult = "Hovered ScrollView has no overflow";
continue;
return false;
}
RuntimeLayoutNode* target = FindDeepestScrollTarget(root, event.position);
if (target == nullptr) {
scrollDebugSnapshot.lastResult = "Scroll target resolution failed";
continue;
return false;
}
const auto found = verticalScrollOffsets.find(target->stateKey);
@@ -592,16 +852,188 @@ bool ApplyScrollWheelInput(
scrollDebugSnapshot.lastOffsetAfter = nextOffset;
if (std::fabs(nextOffset - oldOffset) <= 0.01f) {
scrollDebugSnapshot.lastResult = "Scroll delta clamped to current offset";
continue;
return false;
}
verticalScrollOffsets[target->stateKey] = nextOffset;
++scrollDebugSnapshot.handledWheelEventCount;
scrollDebugSnapshot.lastResult = "Handled";
changed = true;
return true;
}
return changed;
const char* GetInputEventTypeDebugName(UIInputEventType type) {
switch (type) {
case UIInputEventType::PointerMove:
return "PointerMove";
case UIInputEventType::PointerEnter:
return "PointerEnter";
case UIInputEventType::PointerLeave:
return "PointerLeave";
case UIInputEventType::PointerButtonDown:
return "PointerButtonDown";
case UIInputEventType::PointerButtonUp:
return "PointerButtonUp";
case UIInputEventType::PointerWheel:
return "PointerWheel";
case UIInputEventType::KeyDown:
return "KeyDown";
case UIInputEventType::KeyUp:
return "KeyUp";
case UIInputEventType::Character:
return "Character";
case UIInputEventType::FocusGained:
return "FocusGained";
case UIInputEventType::FocusLost:
return "FocusLost";
case UIInputEventType::None:
default:
return "None";
}
}
const char* GetInputTargetKindDebugName(UIInputTargetKind targetKind) {
switch (targetKind) {
case UIInputTargetKind::Hovered:
return "Hovered";
case UIInputTargetKind::Focused:
return "Focused";
case UIInputTargetKind::Captured:
return "Captured";
case UIInputTargetKind::None:
default:
return "None";
}
}
void UpdatePointerTrackingState(
UIPoint& pointerPosition,
bool& hasPointerPosition,
bool& pointerInsideViewport,
const UIInputEvent& event,
const UIRect& viewportRect) {
switch (event.type) {
case UIInputEventType::PointerMove:
case UIInputEventType::PointerButtonDown:
case UIInputEventType::PointerButtonUp:
case UIInputEventType::PointerWheel:
case UIInputEventType::PointerEnter:
pointerPosition = event.position;
hasPointerPosition = true;
pointerInsideViewport = RectContainsPoint(viewportRect, event.position);
return;
case UIInputEventType::PointerLeave:
pointerPosition = event.position;
hasPointerPosition = true;
pointerInsideViewport = false;
return;
default:
return;
}
}
void SanitizeInputDispatcherState(
const RuntimeLayoutNode& root,
UIInputDispatcher& inputDispatcher) {
UIFocusController& focusController = inputDispatcher.GetFocusController();
if (!PathTargetExists(root, focusController.GetFocusedPath())) {
focusController.ClearFocus();
}
if (!PathTargetExists(root, focusController.GetActivePath())) {
focusController.ClearActivePath();
}
if (!PathTargetExists(root, focusController.GetPointerCapturePath())) {
focusController.ClearPointerCapturePath();
}
}
void UpdateInputDebugSnapshot(
const RuntimeLayoutNode& root,
const UIInputPath& hoveredPath,
const UIInputDispatcher& inputDispatcher,
const UIPoint& pointerPosition,
bool pointerInsideViewport,
UIDocumentScreenHost::InputDebugSnapshot& inputDebugSnapshot) {
inputDebugSnapshot.pointerPosition = pointerPosition;
inputDebugSnapshot.pointerInsideViewport = pointerInsideViewport;
inputDebugSnapshot.hoveredStateKey = ResolveStateKeyForPathTarget(root, hoveredPath);
inputDebugSnapshot.focusedStateKey = ResolveStateKeyForPathTarget(
root,
inputDispatcher.GetFocusController().GetFocusedPath());
inputDebugSnapshot.activeStateKey = ResolveStateKeyForPathTarget(
root,
inputDispatcher.GetFocusController().GetActivePath());
inputDebugSnapshot.captureStateKey = ResolveStateKeyForPathTarget(
root,
inputDispatcher.GetFocusController().GetPointerCapturePath());
}
void DispatchInputEvent(
RuntimeLayoutNode& root,
const UIInputEvent& event,
const UIInputPath& hoveredPath,
UIInputDispatcher& inputDispatcher,
UIDocumentScreenHost::InputDebugSnapshot& inputDebugSnapshot) {
if (event.type == UIInputEventType::PointerWheel) {
return;
}
if (UIInputRouter::IsPointerEvent(event.type)) {
++inputDebugSnapshot.totalPointerEventCount;
}
inputDebugSnapshot.lastEventType = GetInputEventTypeDebugName(event.type);
inputDebugSnapshot.lastTargetKind = "None";
inputDebugSnapshot.lastTargetStateKey.clear();
inputDebugSnapshot.lastResult = "No target";
if (event.type == UIInputEventType::FocusLost) {
UIFocusController& focusController = inputDispatcher.GetFocusController();
focusController.ClearPointerCapturePath();
focusController.ClearActivePath();
focusController.ClearFocus();
inputDebugSnapshot.lastResult = "Focus cleared";
return;
}
bool pointerCaptureStarted = false;
const UIInputDispatchSummary summary = inputDispatcher.Dispatch(
event,
hoveredPath,
[&](const UIInputDispatchRequest& request) {
if (!request.isTargetElement) {
return UIInputDispatchDecision{};
}
const RuntimeLayoutNode* node = FindNodeByElementId(root, request.elementId);
if (node == nullptr) {
return UIInputDispatchDecision{};
}
if (event.type == UIInputEventType::PointerButtonDown &&
event.pointerButton == UIPointerButton::Left &&
node->wantsPointerCapture) {
inputDispatcher.GetFocusController().SetPointerCapturePath(node->inputPath);
pointerCaptureStarted = true;
return UIInputDispatchDecision{ true, false };
}
return UIInputDispatchDecision{};
});
inputDebugSnapshot.lastTargetKind = GetInputTargetKindDebugName(summary.routing.plan.targetKind);
inputDebugSnapshot.lastTargetStateKey = ResolveStateKeyForPathTarget(root, summary.routing.plan.targetPath);
inputDebugSnapshot.lastResult = summary.routing.plan.HasTargetPath() ? "Dispatched" : "No target";
if (pointerCaptureStarted) {
inputDebugSnapshot.lastResult = "Pointer capture started";
} else if (event.type == UIInputEventType::PointerButtonUp &&
event.pointerButton == UIPointerButton::Left &&
inputDispatcher.GetFocusController().HasPointerCapture()) {
inputDispatcher.GetFocusController().ClearPointerCapturePath();
inputDebugSnapshot.lastResult = "Pointer capture cleared";
} else if (summary.routing.handled) {
inputDebugSnapshot.lastResult = "Handled";
}
}
void SyncScrollOffsets(
@@ -623,19 +1055,29 @@ void SyncScrollOffsets(
void EmitNode(
const RuntimeLayoutNode& node,
const UIInputPath& hoveredPath,
const UIFocusController& focusController,
UIDrawList& drawList,
UIScreenFrameStats& stats) {
const UIDocumentNode& source = *node.source;
const std::string tagName = ToStdString(source.tagName);
const RuntimeNodeVisualState visualState = ResolveNodeVisualState(
node,
hoveredPath,
focusController);
++stats.nodeCount;
if (tagName == "View" || tagName == "Card" || tagName == "Button") {
drawList.AddFilledRect(node.rect, ToUIColor(ResolveBackgroundColor(source)), 10.0f);
drawList.AddFilledRect(node.rect, ToUIColor(ResolveBackgroundColor(source, visualState)), 10.0f);
++stats.filledRectCommandCount;
if (tagName != "View") {
drawList.AddRectOutline(node.rect, ToUIColor(ResolveBorderColor(source)), 1.0f, 10.0f);
drawList.AddRectOutline(
node.rect,
ToUIColor(ResolveBorderColor(source, visualState)),
ResolveBorderThickness(visualState),
10.0f);
}
}
@@ -674,7 +1116,9 @@ void EmitNode(
drawList.AddText(
UIPoint(node.rect.x + 12.0f, ComputeCenteredTextTop(node.rect, kButtonFontSize)),
ResolveNodeText(source),
ToUIColor(Color(0.95f, 0.97f, 1.0f, 1.0f)),
ToUIColor(visualState.capture || visualState.focused
? Color(1.0f, 1.0f, 1.0f, 1.0f)
: Color(0.95f, 0.97f, 1.0f, 1.0f)),
kButtonFontSize);
++stats.textCommandCount;
}
@@ -688,7 +1132,7 @@ void EmitNode(
}
for (const RuntimeLayoutNode& child : node.children) {
EmitNode(child, drawList, stats);
EmitNode(child, hoveredPath, focusController, drawList, stats);
}
if (pushScrollClip) {
@@ -808,7 +1252,7 @@ UIScreenFrameResult UIDocumentScreenHost::BuildFrame(
const std::string stateRoot = document.sourcePath.empty()
? document.displayName
: document.sourcePath;
RuntimeLayoutNode root = BuildLayoutTree(document.viewDocument.rootNode, stateRoot, 0u);
RuntimeLayoutNode root = BuildLayoutTree(document.viewDocument.rootNode, stateRoot, UIInputPath(), 0u);
MeasureNode(root);
UIRect viewportRect = input.viewportRect;
@@ -819,12 +1263,73 @@ UIScreenFrameResult UIDocumentScreenHost::BuildFrame(
viewportRect.height = (std::max)(360.0f, root.desiredSize.height);
}
if (!input.focused) {
m_pointerState.insideViewport = false;
}
ArrangeNode(root, viewportRect, m_verticalScrollOffsets);
if (ApplyScrollWheelInput(root, input, m_verticalScrollOffsets, m_scrollDebugSnapshot)) {
SanitizeInputDispatcherState(root, m_inputDispatcher);
UIPoint pointerPosition = m_pointerState.position;
bool hasPointerPosition = m_pointerState.hasPosition;
bool pointerInsideViewport =
input.focused &&
m_pointerState.insideViewport &&
(!hasPointerPosition || RectContainsPoint(viewportRect, pointerPosition));
if (!input.focused) {
UIFocusController& focusController = m_inputDispatcher.GetFocusController();
focusController.ClearPointerCapturePath();
focusController.ClearActivePath();
}
for (const UIInputEvent& event : input.events) {
UpdatePointerTrackingState(
pointerPosition,
hasPointerPosition,
pointerInsideViewport,
event,
viewportRect);
pointerInsideViewport = input.focused && pointerInsideViewport;
if (event.type == UIInputEventType::PointerWheel) {
m_inputDebugSnapshot.lastEventType = GetInputEventTypeDebugName(event.type);
m_inputDebugSnapshot.lastTargetKind = "Hovered";
m_inputDebugSnapshot.lastResult = "No hovered ScrollView";
if (ApplyScrollWheelEvent(root, event, m_verticalScrollOffsets, m_scrollDebugSnapshot)) {
ArrangeNode(root, viewportRect, m_verticalScrollOffsets);
}
continue;
}
const UIInputPath eventHoveredPath = ResolveHoveredPath(
root,
pointerPosition,
hasPointerPosition,
pointerInsideViewport);
DispatchInputEvent(root, event, eventHoveredPath, m_inputDispatcher, m_inputDebugSnapshot);
}
const UIInputPath hoveredPath = ResolveHoveredPath(
root,
pointerPosition,
hasPointerPosition,
pointerInsideViewport);
SanitizeInputDispatcherState(root, m_inputDispatcher);
m_pointerState.position = pointerPosition;
m_pointerState.hasPosition = hasPointerPosition;
m_pointerState.insideViewport = pointerInsideViewport;
UpdateInputDebugSnapshot(
root,
hoveredPath,
m_inputDispatcher,
pointerPosition,
pointerInsideViewport,
m_inputDebugSnapshot);
SyncScrollOffsets(root, m_verticalScrollOffsets);
const UIFocusController& focusController = m_inputDispatcher.GetFocusController();
if (const RuntimeLayoutNode* primaryScrollView = FindFirstScrollView(root); primaryScrollView != nullptr) {
m_scrollDebugSnapshot.primaryTargetStateKey = primaryScrollView->stateKey;
m_scrollDebugSnapshot.primaryViewportRect = primaryScrollView->scrollViewportRect;
@@ -838,7 +1343,7 @@ UIScreenFrameResult UIDocumentScreenHost::BuildFrame(
}
UIDrawList& drawList = result.drawData.EmplaceDrawList(document.displayName);
EmitNode(root, drawList, result.stats);
EmitNode(root, hoveredPath, focusController, drawList, result.stats);
result.stats.documentLoaded = true;
result.stats.drawListCount = result.drawData.GetDrawListCount();
@@ -848,6 +1353,10 @@ UIScreenFrameResult UIDocumentScreenHost::BuildFrame(
return result;
}
const UIDocumentScreenHost::InputDebugSnapshot& UIDocumentScreenHost::GetInputDebugSnapshot() const {
return m_inputDebugSnapshot;
}
const UIDocumentScreenHost::ScrollDebugSnapshot& UIDocumentScreenHost::GetScrollDebugSnapshot() const {
return m_scrollDebugSnapshot;
}