From 5342e447af8db9ff5aea4d3ed4204c2649c028ca Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 5 Apr 2026 22:35:24 +0800 Subject: [PATCH] Add XCUI input state validation sandbox batch --- new_editor/src/Application.cpp | 145 +++++++++++++----- new_editor/src/Application.h | 3 + new_editor/ui/views/editor_shell.xcui | 102 +++--------- tests/Core/UI/CMakeLists.txt | 1 + tests/Core/UI/test_ui_input_dispatcher.cpp | 110 +++++++++++++ tests/Core/UI/test_ui_runtime.cpp | 131 ++++++++++++++++ .../test_structured_editor_shell.cpp | 17 +- 7 files changed, 376 insertions(+), 133 deletions(-) create mode 100644 tests/Core/UI/test_ui_input_dispatcher.cpp diff --git a/new_editor/src/Application.cpp b/new_editor/src/Application.cpp index 96b90580..5da8a1b9 100644 --- a/new_editor/src/Application.cpp +++ b/new_editor/src/Application.cpp @@ -27,6 +27,7 @@ using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIInputModifiers; using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIPointerButton; using ::XCEngine::UI::UIRect; using ::XCEngine::UI::Runtime::UIScreenFrameInput; @@ -57,6 +58,19 @@ std::string TruncateText(const std::string& text, std::size_t maxLength) { return text.substr(0, maxLength - 3u) + "..."; } +std::string ExtractStateKeyTail(const std::string& stateKey) { + if (stateKey.empty()) { + return "-"; + } + + const std::size_t separator = stateKey.find_last_of('/'); + if (separator == std::string::npos || separator + 1u >= stateKey.size()) { + return stateKey; + } + + return stateKey.substr(separator + 1u); +} + std::string FormatFloat(float value) { std::ostringstream stream; stream.setf(std::ios::fixed, std::ios::floatfield); @@ -255,6 +269,29 @@ void Application::OnResize(UINT width, UINT height) { m_renderer.Resize(width, height); } +void Application::QueuePointerEvent(UIInputEventType type, UIPointerButton button, WPARAM wParam, LPARAM lParam) { + UIInputEvent event = {}; + event.type = type; + event.pointerButton = button; + event.position = UIPoint( + static_cast(GET_X_LPARAM(lParam)), + static_cast(GET_Y_LPARAM(lParam))); + event.modifiers = BuildInputModifiers(static_cast(wParam)); + m_pendingInputEvents.push_back(event); +} + +void Application::QueuePointerLeaveEvent() { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerLeave; + if (m_hwnd != nullptr) { + POINT clientPoint = {}; + GetCursorPos(&clientPoint); + ScreenToClient(m_hwnd, &clientPoint); + event.position = UIPoint(static_cast(clientPoint.x), static_cast(clientPoint.y)); + } + m_pendingInputEvents.push_back(event); +} + void Application::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) { if (m_hwnd == nullptr) { return; @@ -272,7 +309,6 @@ void Application::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM event.wheelDelta = static_cast(wheelDelta); event.modifiers = BuildInputModifiers(static_cast(wParam)); m_pendingInputEvents.push_back(event); - m_autoScreenshot.RequestCapture("wheel"); } bool Application::LoadStructuredScreen(const char* triggerReason) { @@ -286,10 +322,6 @@ bool Application::LoadStructuredScreen(const char* triggerReason) { m_runtimeStatus = loaded ? "Authored XCUI" : "Fallback Sandbox"; m_runtimeError = loaded ? std::string() : m_screenPlayer.GetLastError(); RebuildTrackedFileStates(); - - std::string screenshotReason = triggerReason != nullptr ? triggerReason : "update"; - screenshotReason += loaded ? "_authored" : "_fallback"; - m_autoScreenshot.RequestCapture(std::move(screenshotReason)); return loaded; } @@ -380,47 +412,31 @@ void Application::AppendRuntimeOverlay(UIDrawData& drawData, float width, float : "Using native fallback while authored UI is invalid."); if (authoredMode) { - const auto& scrollDebug = m_documentHost.GetScrollDebugSnapshot(); - if (!scrollDebug.primaryTargetStateKey.empty()) { - detailLines.push_back( - "Primary: " + - TruncateText(scrollDebug.primaryTargetStateKey, 52u)); - detailLines.push_back( - "Primary viewport: " + - FormatRect(scrollDebug.primaryViewportRect)); - detailLines.push_back( - "Primary overflow: " + - FormatFloat(scrollDebug.primaryOverflow)); - } + const auto& inputDebug = m_documentHost.GetInputDebugSnapshot(); detailLines.push_back( - "Wheel total/handled: " + - std::to_string(scrollDebug.totalWheelEventCount) + - " / " + - std::to_string(scrollDebug.handledWheelEventCount)); - if (scrollDebug.totalWheelEventCount > 0u) { + "Hover | Focus: " + + ExtractStateKeyTail(inputDebug.hoveredStateKey) + + " | " + + ExtractStateKeyTail(inputDebug.focusedStateKey)); + detailLines.push_back( + "Active | Capture: " + + ExtractStateKeyTail(inputDebug.activeStateKey) + + " | " + + ExtractStateKeyTail(inputDebug.captureStateKey)); + if (!inputDebug.lastEventType.empty()) { detailLines.push_back( - "Last wheel " + - FormatFloat(scrollDebug.lastWheelDelta) + + "Last input: " + + inputDebug.lastEventType + " at " + - FormatPoint(scrollDebug.lastPointerPosition)); + FormatPoint(inputDebug.pointerPosition)); + detailLines.push_back( + "Route: " + + inputDebug.lastTargetKind + + " -> " + + ExtractStateKeyTail(inputDebug.lastTargetStateKey)); detailLines.push_back( "Result: " + - (scrollDebug.lastResult.empty() ? std::string("n/a") : scrollDebug.lastResult)); - if (!scrollDebug.lastTargetStateKey.empty()) { - detailLines.push_back( - "Target: " + - TruncateText(scrollDebug.lastTargetStateKey, 52u)); - detailLines.push_back( - "Viewport: " + - FormatRect(scrollDebug.lastViewportRect)); - detailLines.push_back( - "Overflow/offset: " + - FormatFloat(scrollDebug.lastOverflow) + - " | " + - FormatFloat(scrollDebug.lastOffsetBefore) + - " -> " + - FormatFloat(scrollDebug.lastOffsetAfter)); - } + (inputDebug.lastResult.empty() ? std::string("n/a") : inputDebug.lastResult)); } } @@ -428,6 +444,8 @@ void Application::AppendRuntimeOverlay(UIDrawData& drawData, float width, float detailLines.push_back("Shot pending..."); } else if (!m_autoScreenshot.GetLastCaptureSummary().empty()) { detailLines.push_back(TruncateText(m_autoScreenshot.GetLastCaptureSummary(), 78u)); + } else { + detailLines.push_back("Screenshots: manual only (F12)"); } if (!m_runtimeError.empty()) { @@ -494,12 +512,57 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP return 0; } break; + case WM_MOUSEMOVE: + if (application != nullptr) { + if (!application->m_trackingMouseLeave) { + TRACKMOUSEEVENT trackMouseEvent = {}; + trackMouseEvent.cbSize = sizeof(trackMouseEvent); + trackMouseEvent.dwFlags = TME_LEAVE; + trackMouseEvent.hwndTrack = hwnd; + if (TrackMouseEvent(&trackMouseEvent)) { + application->m_trackingMouseLeave = true; + } + } + application->QueuePointerEvent(UIInputEventType::PointerMove, UIPointerButton::None, wParam, lParam); + return 0; + } + break; + case WM_MOUSELEAVE: + if (application != nullptr) { + application->m_trackingMouseLeave = false; + application->QueuePointerLeaveEvent(); + return 0; + } + break; + case WM_LBUTTONDOWN: + if (application != nullptr) { + SetFocus(hwnd); + SetCapture(hwnd); + application->QueuePointerEvent(UIInputEventType::PointerButtonDown, UIPointerButton::Left, wParam, lParam); + return 0; + } + break; + case WM_LBUTTONUP: + if (application != nullptr) { + if (GetCapture() == hwnd) { + ReleaseCapture(); + } + application->QueuePointerEvent(UIInputEventType::PointerButtonUp, UIPointerButton::Left, wParam, lParam); + return 0; + } + break; case WM_MOUSEWHEEL: if (application != nullptr) { application->QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam); return 0; } break; + case WM_KEYDOWN: + if (application != nullptr && wParam == VK_F12) { + application->m_autoScreenshot.RequestCapture("manual_f12"); + return 0; + } + break; case WM_ERASEBKGND: return 1; case WM_DESTROY: diff --git a/new_editor/src/Application.h b/new_editor/src/Application.h index 8412a452..2940b3d7 100644 --- a/new_editor/src/Application.h +++ b/new_editor/src/Application.h @@ -41,6 +41,8 @@ private: void Shutdown(); void RenderFrame(); void OnResize(UINT width, UINT height); + void QueuePointerEvent(::XCEngine::UI::UIInputEventType type, ::XCEngine::UI::UIPointerButton button, WPARAM wParam, LPARAM lParam); + void QueuePointerLeaveEvent(); void QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam); bool LoadStructuredScreen(const char* triggerReason); void RefreshStructuredScreen(); @@ -63,6 +65,7 @@ private: std::chrono::steady_clock::time_point m_lastReloadPollTime = {}; std::uint64_t m_frameIndex = 0; std::vector<::XCEngine::UI::UIInputEvent> m_pendingInputEvents = {}; + bool m_trackingMouseLeave = false; bool m_useStructuredScreen = false; std::string m_runtimeStatus = {}; std::string m_runtimeError = {}; diff --git a/new_editor/ui/views/editor_shell.xcui b/new_editor/ui/views/editor_shell.xcui index e07afc1a..008ac0cb 100644 --- a/new_editor/ui/views/editor_shell.xcui +++ b/new_editor/ui/views/editor_shell.xcui @@ -1,92 +1,30 @@ - + - -