Add XCUI core scroll view validation in new_editor

This commit is contained in:
2026-04-05 21:27:00 +08:00
parent 05debc0499
commit 7812b92992
6 changed files with 518 additions and 34 deletions

View File

@@ -5,6 +5,7 @@
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <sstream>
#include <string>
#include <system_error>
#include <unordered_set>
@@ -22,6 +23,9 @@ namespace {
using ::XCEngine::UI::UIColor;
using ::XCEngine::UI::UIDrawData;
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIInputEvent;
using ::XCEngine::UI::UIInputEventType;
using ::XCEngine::UI::UIInputModifiers;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
using ::XCEngine::UI::Runtime::UIScreenFrameInput;
@@ -53,6 +57,35 @@ std::string TruncateText(const std::string& text, std::size_t maxLength) {
return text.substr(0, maxLength - 3u) + "...";
}
std::string FormatFloat(float value) {
std::ostringstream stream;
stream.setf(std::ios::fixed, std::ios::floatfield);
stream.precision(1);
stream << value;
return stream.str();
}
std::string FormatPoint(const UIPoint& point) {
return "(" + FormatFloat(point.x) + ", " + FormatFloat(point.y) + ")";
}
std::string FormatRect(const UIRect& rect) {
return "(" + FormatFloat(rect.x) +
", " + FormatFloat(rect.y) +
", " + FormatFloat(rect.width) +
", " + FormatFloat(rect.height) +
")";
}
UIInputModifiers BuildInputModifiers(size_t wParam) {
UIInputModifiers modifiers = {};
modifiers.shift = (wParam & MK_SHIFT) != 0;
modifiers.control = (wParam & MK_CONTROL) != 0;
modifiers.alt = (GetKeyState(VK_MENU) & 0x8000) != 0;
modifiers.super = (GetKeyState(VK_LWIN) & 0x8000) != 0 || (GetKeyState(VK_RWIN) & 0x8000) != 0;
return modifiers;
}
} // namespace
Application::Application()
@@ -170,11 +203,14 @@ void Application::RenderFrame() {
m_lastFrameTime = now;
RefreshStructuredScreen();
std::vector<UIInputEvent> frameEvents = std::move(m_pendingInputEvents);
m_pendingInputEvents.clear();
UIDrawData drawData = {};
if (m_useStructuredScreen && m_screenPlayer.IsLoaded()) {
UIScreenFrameInput input = {};
input.viewportRect = UIRect(0.0f, 0.0f, width, height);
input.events = std::move(frameEvents);
input.deltaTimeSeconds = deltaTimeSeconds;
input.frameIndex = ++m_frameIndex;
input.focused = GetForegroundWindow() == m_hwnd;
@@ -219,6 +255,26 @@ void Application::OnResize(UINT width, UINT height) {
m_renderer.Resize(width, height);
}
void Application::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) {
if (m_hwnd == nullptr) {
return;
}
POINT screenPoint = {
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam)
};
ScreenToClient(m_hwnd, &screenPoint);
UIInputEvent event = {};
event.type = UIInputEventType::PointerWheel;
event.position = UIPoint(static_cast<float>(screenPoint.x), static_cast<float>(screenPoint.y));
event.wheelDelta = static_cast<float>(wheelDelta);
event.modifiers = BuildInputModifiers(static_cast<size_t>(wParam));
m_pendingInputEvents.push_back(event);
m_autoScreenshot.RequestCapture("wheel");
}
bool Application::LoadStructuredScreen(const char* triggerReason) {
m_screenAsset = {};
m_screenAsset.screenId = "new_editor.editor_shell";
@@ -316,13 +372,58 @@ bool Application::DetectTrackedFileChange() const {
void Application::AppendRuntimeOverlay(UIDrawData& drawData, float width, float height) const {
const bool authoredMode = m_useStructuredScreen && m_screenPlayer.IsLoaded();
const float panelWidth = authoredMode ? 256.0f : 360.0f;
const float panelWidth = authoredMode ? 420.0f : 360.0f;
std::vector<std::string> detailLines = {};
detailLines.push_back(
authoredMode
? "Hot reload watches authored UI resources."
: "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));
}
detailLines.push_back(
"Wheel total/handled: " +
std::to_string(scrollDebug.totalWheelEventCount) +
" / " +
std::to_string(scrollDebug.handledWheelEventCount));
if (scrollDebug.totalWheelEventCount > 0u) {
detailLines.push_back(
"Last wheel " +
FormatFloat(scrollDebug.lastWheelDelta) +
" at " +
FormatPoint(scrollDebug.lastPointerPosition));
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));
}
}
}
if (m_autoScreenshot.HasPendingCapture()) {
detailLines.push_back("Shot pending...");
} else if (!m_autoScreenshot.GetLastCaptureSummary().empty()) {
@@ -393,6 +494,12 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
return 0;
}
break;
case WM_MOUSEWHEEL:
if (application != nullptr) {
application->QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam);
return 0;
}
break;
case WM_ERASEBKGND:
return 1;
case WM_DESTROY:

View File

@@ -11,6 +11,7 @@
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
#include <windows.h>
#include <windowsx.h>
#include <chrono>
#include <cstdint>
@@ -40,6 +41,7 @@ private:
void Shutdown();
void RenderFrame();
void OnResize(UINT width, UINT height);
void QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam);
bool LoadStructuredScreen(const char* triggerReason);
void RefreshStructuredScreen();
void RebuildTrackedFileStates();
@@ -60,6 +62,7 @@ private:
std::chrono::steady_clock::time_point m_lastFrameTime = {};
std::chrono::steady_clock::time_point m_lastReloadPollTime = {};
std::uint64_t m_frameIndex = 0;
std::vector<::XCEngine::UI::UIInputEvent> m_pendingInputEvents = {};
bool m_useStructuredScreen = false;
std::string m_runtimeStatus = {};
std::string m_runtimeError = {};

View File

@@ -46,13 +46,23 @@
</Column>
</Card>
<Card title="Console" subtitle="runtime smoke log" height="220">
<Column gap="8">
<Text text="Info XCUI authored screen loaded." />
<Text text="Info Theme + schema resources are tracked for reload." />
<Text text="Warn Viewport host stays out of scope in this phase." />
<Text text="Todo Replace shell placeholders with Editor widgets." />
</Column>
<Card title="Console" subtitle="wheel-scroll core validation" height="220">
<ScrollView id="console-scroll" height="fill">
<Column gap="8">
<Text text="Scroll here with the mouse wheel." />
<Text text="Check that content clips cleanly inside this card." />
<Text text="Info XCUI authored screen loaded." />
<Text text="Info Theme + schema resources are tracked for reload." />
<Text text="Warn Viewport host stays out of scope in this phase." />
<Text text="Todo Replace shell placeholders with Editor widgets." />
<Text text="Trace ScrollView should retain offset across frames." />
<Text text="Trace Wheel input should only affect the hovered view." />
<Text text="Trace Hidden rows must stay clipped below the footer." />
<Text text="Trace Resize should not corrupt the scroll offset clamp." />
<Text text="Trace Hot reload should rebuild layout without tearing." />
<Text text="Trace This panel exists only to validate XCUI core scroll." />
</Column>
</ScrollView>
</Card>
</Column>