Fix UI document host input integration build
This commit is contained in:
@@ -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 = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user