Refactor XCUI editor module layout
This commit is contained in:
@@ -25,6 +25,12 @@ endif()
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/number_field_basic/CMakeLists.txt")
|
||||
add_subdirectory(number_field_basic)
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/asset_field_basic/CMakeLists.txt")
|
||||
add_subdirectory(asset_field_basic)
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/object_field_basic/CMakeLists.txt")
|
||||
add_subdirectory(object_field_basic)
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/text_field_basic/CMakeLists.txt")
|
||||
add_subdirectory(text_field_basic)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
add_executable(editor_ui_asset_field_basic_validation WIN32
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(editor_ui_asset_field_basic_validation PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Editor/integration/shared/src
|
||||
${CMAKE_SOURCE_DIR}/new_editor/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/app
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
target_compile_definitions(editor_ui_asset_field_basic_validation PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
XCENGINE_EDITOR_UI_TESTS_REPO_ROOT="${XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH}"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(editor_ui_asset_field_basic_validation PRIVATE /utf-8 /FS)
|
||||
set_property(TARGET editor_ui_asset_field_basic_validation PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
|
||||
target_link_libraries(editor_ui_asset_field_basic_validation PRIVATE
|
||||
XCUIEditorLib
|
||||
XCUIEditorHost
|
||||
)
|
||||
|
||||
set_target_properties(editor_ui_asset_field_basic_validation PROPERTIES
|
||||
OUTPUT_NAME "XCUIEditorAssetFieldBasicValidation"
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
736
tests/UI/Editor/integration/shell/asset_field_basic/main.cpp
Normal file
736
tests/UI/Editor/integration/shell/asset_field_basic/main.cpp
Normal file
@@ -0,0 +1,736 @@
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorAssetField.h>
|
||||
#include <XCEditor/Fields/UIEditorAssetFieldInteraction.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT
|
||||
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
using XCEngine::UI::UIColor;
|
||||
using XCEngine::UI::UIDrawData;
|
||||
using XCEngine::UI::UIDrawList;
|
||||
using XCEngine::UI::UIInputEvent;
|
||||
using XCEngine::UI::UIInputEventType;
|
||||
using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::Host::AutoScreenshotController;
|
||||
using XCEngine::UI::Editor::Host::NativeRenderer;
|
||||
using XCEngine::UI::Editor::UIEditorAssetFieldInteractionFrame;
|
||||
using XCEngine::UI::Editor::UIEditorAssetFieldInteractionResult;
|
||||
using XCEngine::UI::Editor::UIEditorAssetFieldInteractionState;
|
||||
using XCEngine::UI::Editor::UpdateUIEditorAssetFieldInteraction;
|
||||
using XCEngine::UI::Editor::Widgets::AppendUIEditorAssetField;
|
||||
using XCEngine::UI::Editor::Widgets::HitTestUIEditorAssetField;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldPalette;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldSpec;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorAssetFieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | AssetField Basic";
|
||||
|
||||
enum class ActionId : unsigned char {
|
||||
None = 0,
|
||||
Reset,
|
||||
Capture
|
||||
};
|
||||
|
||||
struct ButtonLayout {
|
||||
ActionId action = ActionId::None;
|
||||
const char* label = "";
|
||||
UIRect rect = {};
|
||||
};
|
||||
|
||||
struct ScenarioLayout {
|
||||
UIRect introRect = {};
|
||||
UIRect controlRect = {};
|
||||
UIRect stateRect = {};
|
||||
UIRect previewRect = {};
|
||||
UIRect fieldRect = {};
|
||||
std::vector<ButtonLayout> buttons = {};
|
||||
};
|
||||
|
||||
struct SampleAsset {
|
||||
const char* assetId = "";
|
||||
const char* displayName = "";
|
||||
const char* statusText = "";
|
||||
UIColor tint = {};
|
||||
};
|
||||
|
||||
constexpr SampleAsset kSampleAssets[] = {
|
||||
{
|
||||
"assets/textures/crate_albedo",
|
||||
"Crate_Albedo",
|
||||
"Ready",
|
||||
UIColor(0.30f, 0.55f, 0.84f, 1.0f)
|
||||
},
|
||||
{
|
||||
"assets/materials/sci_fi_panel",
|
||||
"SciFi_Panel",
|
||||
"Dirty",
|
||||
UIColor(0.77f, 0.56f, 0.28f, 1.0f)
|
||||
}
|
||||
};
|
||||
|
||||
std::filesystem::path ResolveRepoRootPath() {
|
||||
std::string root = XCENGINE_EDITOR_UI_TESTS_REPO_ROOT;
|
||||
if (root.size() >= 2u && root.front() == '"' && root.back() == '"') {
|
||||
root = root.substr(1u, root.size() - 2u);
|
||||
}
|
||||
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
y >= rect.y &&
|
||||
y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
std::int32_t MapAssetFieldKey(UINT keyCode) {
|
||||
switch (keyCode) {
|
||||
case VK_SPACE:
|
||||
return static_cast<std::int32_t>(KeyCode::Space);
|
||||
case VK_RETURN:
|
||||
return static_cast<std::int32_t>(KeyCode::Enter);
|
||||
case VK_DELETE:
|
||||
return static_cast<std::int32_t>(KeyCode::Delete);
|
||||
case VK_BACK:
|
||||
return static_cast<std::int32_t>(KeyCode::Backspace);
|
||||
default:
|
||||
return static_cast<std::int32_t>(KeyCode::None);
|
||||
}
|
||||
}
|
||||
|
||||
ScenarioLayout BuildScenarioLayout(
|
||||
float width,
|
||||
float height,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics) {
|
||||
const float margin = shellMetrics.margin;
|
||||
constexpr float leftWidth = 470.0f;
|
||||
const float gap = shellMetrics.gap;
|
||||
|
||||
ScenarioLayout layout = {};
|
||||
layout.introRect = UIRect(margin, margin, leftWidth, 260.0f);
|
||||
layout.controlRect = UIRect(margin, layout.introRect.y + layout.introRect.height + gap, leftWidth, 84.0f);
|
||||
layout.stateRect = UIRect(
|
||||
margin,
|
||||
layout.controlRect.y + layout.controlRect.height + gap,
|
||||
leftWidth,
|
||||
(std::max)(220.0f, height - (layout.controlRect.y + layout.controlRect.height + gap) - margin));
|
||||
layout.previewRect = UIRect(
|
||||
leftWidth + margin * 2.0f,
|
||||
margin,
|
||||
(std::max)(420.0f, width - leftWidth - margin * 3.0f),
|
||||
height - margin * 2.0f);
|
||||
layout.fieldRect = UIRect(
|
||||
layout.previewRect.x + 28.0f,
|
||||
layout.previewRect.y + 96.0f,
|
||||
(std::min)(460.0f, layout.previewRect.width - 56.0f),
|
||||
22.0f);
|
||||
|
||||
const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f;
|
||||
const float buttonY = layout.controlRect.y + 32.0f;
|
||||
layout.buttons = {
|
||||
{ ActionId::Reset, "重置", UIRect(layout.controlRect.x + 14.0f, buttonY, buttonWidth, 36.0f) },
|
||||
{ ActionId::Capture, "截图(F12)", UIRect(layout.controlRect.x + 26.0f + buttonWidth, buttonY, buttonWidth, 36.0f) }
|
||||
};
|
||||
return layout;
|
||||
}
|
||||
|
||||
void DrawCard(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics,
|
||||
std::string_view title,
|
||||
std::string_view subtitle = {}) {
|
||||
drawList.AddFilledRect(rect, shellPalette.cardBackground, shellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(rect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius);
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + 16.0f, rect.y + 14.0f),
|
||||
std::string(title),
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.titleFontSize);
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + 16.0f, rect.y + 40.0f),
|
||||
std::string(subtitle),
|
||||
shellPalette.textMuted,
|
||||
shellMetrics.bodyFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawButton(
|
||||
UIDrawList& drawList,
|
||||
const ButtonLayout& button,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics,
|
||||
bool hovered) {
|
||||
drawList.AddFilledRect(
|
||||
button.rect,
|
||||
hovered ? shellPalette.buttonHoverBackground : shellPalette.buttonBackground,
|
||||
shellMetrics.buttonRadius);
|
||||
drawList.AddRectOutline(button.rect, shellPalette.cardBorder, 1.0f, shellMetrics.buttonRadius);
|
||||
drawList.AddText(
|
||||
UIPoint(button.rect.x + 16.0f, button.rect.y + 10.0f),
|
||||
button.label,
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
}
|
||||
|
||||
std::string DescribeHitTarget(const UIEditorAssetFieldHitTarget& hitTarget) {
|
||||
switch (hitTarget.kind) {
|
||||
case UIEditorAssetFieldHitTargetKind::ValueBox:
|
||||
return "value_box";
|
||||
case UIEditorAssetFieldHitTargetKind::PickerButton:
|
||||
return "picker_button";
|
||||
case UIEditorAssetFieldHitTargetKind::ClearButton:
|
||||
return "clear_button";
|
||||
case UIEditorAssetFieldHitTargetKind::Row:
|
||||
return "row";
|
||||
case UIEditorAssetFieldHitTargetKind::None:
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
UIInputEvent MakePointerEvent(
|
||||
UIInputEventType type,
|
||||
const UIPoint& position,
|
||||
UIPointerButton button = UIPointerButton::None) {
|
||||
UIInputEvent event = {};
|
||||
event.type = type;
|
||||
event.position = position;
|
||||
event.pointerButton = button;
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakeKeyEvent(std::int32_t keyCode) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::KeyDown;
|
||||
event.keyCode = keyCode;
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakeFocusEvent(UIInputEventType type) {
|
||||
UIInputEvent event = {};
|
||||
event.type = type;
|
||||
return event;
|
||||
}
|
||||
|
||||
class ScenarioApp {
|
||||
public:
|
||||
int Run(HINSTANCE hInstance, int nCmdShow) {
|
||||
if (!Initialize(hInstance, nCmdShow)) {
|
||||
Shutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
MSG message = {};
|
||||
while (message.message != WM_QUIT) {
|
||||
if (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) {
|
||||
TranslateMessage(&message);
|
||||
DispatchMessageW(&message);
|
||||
continue;
|
||||
}
|
||||
|
||||
RenderFrame();
|
||||
Sleep(8);
|
||||
}
|
||||
|
||||
Shutdown();
|
||||
return static_cast<int>(message.wParam);
|
||||
}
|
||||
|
||||
private:
|
||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
if (message == WM_NCCREATE) {
|
||||
const auto* createStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
|
||||
auto* app = static_cast<ScenarioApp*>(createStruct->lpCreateParams);
|
||||
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(app));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
auto* app = reinterpret_cast<ScenarioApp*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||
if (app == nullptr) {
|
||||
return DefWindowProcW(hwnd, message, wParam, lParam);
|
||||
}
|
||||
return app->HandleMessage(hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
bool Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
m_hInstance = hInstance;
|
||||
|
||||
WNDCLASSEXW windowClass = {};
|
||||
windowClass.cbSize = sizeof(windowClass);
|
||||
windowClass.style = CS_HREDRAW | CS_VREDRAW;
|
||||
windowClass.lpfnWndProc = &ScenarioApp::WndProc;
|
||||
windowClass.hInstance = hInstance;
|
||||
windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
||||
windowClass.lpszClassName = kWindowClassName;
|
||||
|
||||
m_windowClassAtom = RegisterClassExW(&windowClass);
|
||||
if (m_windowClassAtom == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hwnd = CreateWindowExW(
|
||||
0,
|
||||
kWindowClassName,
|
||||
kWindowTitle,
|
||||
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
1440,
|
||||
860,
|
||||
nullptr,
|
||||
nullptr,
|
||||
hInstance,
|
||||
this);
|
||||
if (m_hwnd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ShowWindow(m_hwnd, nCmdShow);
|
||||
UpdateWindow(m_hwnd);
|
||||
|
||||
if (!m_renderer.Initialize(m_hwnd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
m_shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
m_fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorAssetFieldMetrics();
|
||||
m_fieldPalette = XCEngine::UI::Editor::ResolveUIEditorAssetFieldPalette();
|
||||
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/asset_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
ResetScenario();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
m_autoScreenshot.Shutdown();
|
||||
m_renderer.Shutdown();
|
||||
|
||||
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
|
||||
DestroyWindow(m_hwnd);
|
||||
}
|
||||
m_hwnd = nullptr;
|
||||
|
||||
if (m_windowClassAtom != 0 && m_hInstance != nullptr) {
|
||||
UnregisterClassW(kWindowClassName, m_hInstance);
|
||||
m_windowClassAtom = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
m_spec = {};
|
||||
m_spec.fieldId = "renderer.base_map";
|
||||
m_spec.label = "Base Map";
|
||||
m_spec.emptyText = "None (Asset)";
|
||||
ApplySampleAsset(0u);
|
||||
m_interactionState = {};
|
||||
m_lastResult = "已重置到默认 AssetField 状态";
|
||||
m_activateCount = 0u;
|
||||
m_pressedAction = ActionId::None;
|
||||
}
|
||||
|
||||
void ApplySampleAsset(std::size_t sampleIndex) {
|
||||
m_currentSampleIndex = sampleIndex % std::size(kSampleAssets);
|
||||
const SampleAsset& sample = kSampleAssets[m_currentSampleIndex];
|
||||
m_spec.assetId = sample.assetId;
|
||||
m_spec.displayName = sample.displayName;
|
||||
m_spec.statusText = sample.statusText;
|
||||
m_spec.tint = sample.tint;
|
||||
}
|
||||
|
||||
void ApplyNextSampleAsset() {
|
||||
ApplySampleAsset(m_currentSampleIndex + 1u);
|
||||
}
|
||||
|
||||
ScenarioLayout GetLayout() const {
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
|
||||
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
|
||||
return BuildScenarioLayout(width, height, m_shellMetrics);
|
||||
}
|
||||
|
||||
void RefreshFrame(const UIRect& fieldRect) {
|
||||
m_frame = UpdateUIEditorAssetFieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
fieldRect,
|
||||
{},
|
||||
m_fieldMetrics);
|
||||
}
|
||||
|
||||
void RenderFrame() {
|
||||
if (m_hwnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
|
||||
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, m_shellMetrics);
|
||||
|
||||
std::vector<UIInputEvent> events = std::move(m_pendingInputEvents);
|
||||
m_pendingInputEvents.clear();
|
||||
|
||||
m_frame = UpdateUIEditorAssetFieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
layout.fieldRect,
|
||||
events,
|
||||
m_fieldMetrics);
|
||||
ApplyInteractionResult(m_frame.result, layout.fieldRect);
|
||||
|
||||
const UIEditorAssetFieldHitTarget currentHit =
|
||||
HitTestUIEditorAssetField(m_frame.layout, m_mousePosition);
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorAssetFieldBasic");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), m_shellPalette.windowBackground);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.introRect,
|
||||
m_shellPalette,
|
||||
m_shellMetrics,
|
||||
"这个测试在验证什么功能?",
|
||||
"验证 Editor 基础 AssetField 的固定风格、清晰 hit target,以及 activate / picker / clear 三类最小请求。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 74.0f),
|
||||
"1. 字段视觉固定为 Unity 风格对象槽:标签、预览块、值文本、状态标记、选择按钮、清空按钮。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 98.0f),
|
||||
"2. 点击值区域只产生 activateRequested;基础层不决定 ping、打开面板或业务跳转。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 122.0f),
|
||||
"3. 点击 o,或 focused 后按 Enter / Space,只产生 pickerRequested。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 146.0f),
|
||||
"4. 点击 X,或 focused 后按 Delete / Backspace,清空当前引用。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 170.0f),
|
||||
"5. 本壳程序只用两个固定样本资产模拟 picker 外部响应,不引入 object picker 弹窗系统。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 194.0f),
|
||||
"6. 左侧状态区观察 hover / focus / assetId / displayName / status / result;F12 截图。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(drawList, layout.controlRect, m_shellPalette, m_shellMetrics, "操作");
|
||||
for (const ButtonLayout& button : layout.buttons) {
|
||||
DrawButton(
|
||||
drawList,
|
||||
button,
|
||||
m_shellPalette,
|
||||
m_shellMetrics,
|
||||
ContainsPoint(button.rect, m_mousePosition.x, m_mousePosition.y));
|
||||
}
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.stateRect,
|
||||
m_shellPalette,
|
||||
m_shellMetrics,
|
||||
"状态摘要",
|
||||
"重点看 hit、focus、值是否变化,以及请求是否保持薄语义。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 72.0f),
|
||||
"Hover: " + DescribeHitTarget(currentHit),
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 96.0f),
|
||||
std::string("Focused: ") + (m_interactionState.fieldState.focused ? "yes" : "no"),
|
||||
m_interactionState.fieldState.focused ? m_shellPalette.textSuccess : m_shellPalette.textMuted,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 120.0f),
|
||||
"AssetId: " + (m_spec.assetId.empty() ? std::string("(empty)") : m_spec.assetId),
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 144.0f),
|
||||
"Display: " + (m_spec.displayName.empty() ? std::string("(empty)") : m_spec.displayName),
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 168.0f),
|
||||
"Status: " + (m_spec.statusText.empty() ? std::string("(empty)") : m_spec.statusText),
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 192.0f),
|
||||
"Activate Count: " + std::to_string(m_activateCount),
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 216.0f),
|
||||
"Result: " + m_lastResult,
|
||||
m_shellPalette.textMuted,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 240.0f),
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
? "截图排队中..."
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("F12 -> tests/UI/Editor/integration/shell/asset_field_basic/captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary()),
|
||||
m_shellPalette.textWeak,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.previewRect,
|
||||
m_shellPalette,
|
||||
m_shellMetrics,
|
||||
"AssetField 预览",
|
||||
"这里只放一个基础 AssetField,不接业务面板,也不接 picker。");
|
||||
AppendUIEditorAssetField(
|
||||
drawList,
|
||||
layout.fieldRect,
|
||||
m_spec,
|
||||
m_interactionState.fieldState,
|
||||
m_fieldPalette,
|
||||
m_fieldMetrics);
|
||||
|
||||
const bool framePresented = m_renderer.Render(drawData);
|
||||
m_autoScreenshot.CaptureIfRequested(
|
||||
m_renderer,
|
||||
drawData,
|
||||
static_cast<unsigned int>(width),
|
||||
static_cast<unsigned int>(height),
|
||||
framePresented);
|
||||
}
|
||||
|
||||
void ApplyInteractionResult(
|
||||
const UIEditorAssetFieldInteractionResult& result,
|
||||
const UIRect& fieldRect) {
|
||||
bool refreshNeeded = false;
|
||||
|
||||
if (result.pickerRequested) {
|
||||
ApplyNextSampleAsset();
|
||||
m_lastResult = "收到 pickerRequested,壳程序已用固定样本资产模拟外部选择结果";
|
||||
refreshNeeded = true;
|
||||
} else if (result.clearRequested) {
|
||||
m_lastResult = "收到 clearRequested,当前引用已清空";
|
||||
refreshNeeded = true;
|
||||
} else if (result.activateRequested) {
|
||||
++m_activateCount;
|
||||
m_lastResult = "收到 activateRequested;基础层只发请求,不绑定业务动作";
|
||||
} else if (result.focusChanged) {
|
||||
m_lastResult = std::string("焦点变化: ") +
|
||||
(m_interactionState.fieldState.focused ? "focused" : "lost");
|
||||
} else if (result.consumed) {
|
||||
m_lastResult = "输入已被当前字段消费";
|
||||
}
|
||||
|
||||
if (refreshNeeded) {
|
||||
RefreshFrame(fieldRect);
|
||||
}
|
||||
}
|
||||
|
||||
void ExecuteAction(ActionId action) {
|
||||
switch (action) {
|
||||
case ActionId::Reset:
|
||||
ResetScenario();
|
||||
break;
|
||||
|
||||
case ActionId::Capture:
|
||||
m_autoScreenshot.RequestCapture("manual_button");
|
||||
m_lastResult = "已请求截图,输出到 captures/latest.png";
|
||||
break;
|
||||
|
||||
case ActionId::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const ButtonLayout* HitTestAction(const ScenarioLayout& layout, float x, float y) const {
|
||||
for (const ButtonLayout& button : layout.buttons) {
|
||||
if (ContainsPoint(button.rect, x, y)) {
|
||||
return &button;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_CLOSE:
|
||||
DestroyWindow(hwnd);
|
||||
return 0;
|
||||
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
|
||||
case WM_SIZE:
|
||||
if (wParam != SIZE_MINIMIZED) {
|
||||
m_renderer.Resize(static_cast<UINT>(LOWORD(lParam)), static_cast<UINT>(HIWORD(lParam)));
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_MOUSEMOVE: {
|
||||
m_mousePosition = UIPoint(
|
||||
static_cast<float>(GET_X_LPARAM(lParam)),
|
||||
static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
TRACKMOUSEEVENT trackEvent = {};
|
||||
trackEvent.cbSize = sizeof(trackEvent);
|
||||
trackEvent.dwFlags = TME_LEAVE;
|
||||
trackEvent.hwndTrack = hwnd;
|
||||
TrackMouseEvent(&trackEvent);
|
||||
|
||||
m_pendingInputEvents.push_back(MakePointerEvent(UIInputEventType::PointerMove, m_mousePosition));
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_MOUSELEAVE:
|
||||
m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
m_pendingInputEvents.push_back(MakePointerEvent(UIInputEventType::PointerLeave, m_mousePosition));
|
||||
return 0;
|
||||
|
||||
case WM_LBUTTONDOWN: {
|
||||
SetFocus(hwnd);
|
||||
m_mousePosition = UIPoint(
|
||||
static_cast<float>(GET_X_LPARAM(lParam)),
|
||||
static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const ButtonLayout* button = HitTestAction(layout, m_mousePosition.x, m_mousePosition.y);
|
||||
m_pressedAction = button != nullptr ? button->action : ActionId::None;
|
||||
if (button == nullptr) {
|
||||
m_pendingInputEvents.push_back(
|
||||
MakePointerEvent(UIInputEventType::PointerButtonDown, m_mousePosition, UIPointerButton::Left));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_LBUTTONUP: {
|
||||
m_mousePosition = UIPoint(
|
||||
static_cast<float>(GET_X_LPARAM(lParam)),
|
||||
static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const ButtonLayout* button = HitTestAction(layout, m_mousePosition.x, m_mousePosition.y);
|
||||
if (m_pressedAction != ActionId::None &&
|
||||
button != nullptr &&
|
||||
button->action == m_pressedAction) {
|
||||
ExecuteAction(button->action);
|
||||
} else {
|
||||
m_pendingInputEvents.push_back(
|
||||
MakePointerEvent(UIInputEventType::PointerButtonUp, m_mousePosition, UIPointerButton::Left));
|
||||
}
|
||||
m_pressedAction = ActionId::None;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_SETFOCUS:
|
||||
m_pendingInputEvents.push_back(MakeFocusEvent(UIInputEventType::FocusGained));
|
||||
return 0;
|
||||
|
||||
case WM_KILLFOCUS:
|
||||
m_pendingInputEvents.push_back(MakeFocusEvent(UIInputEventType::FocusLost));
|
||||
return 0;
|
||||
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
if (wParam == VK_F12) {
|
||||
m_autoScreenshot.RequestCapture("manual_f12");
|
||||
m_lastResult = "已请求截图,输出到 captures/latest.png";
|
||||
return 0;
|
||||
}
|
||||
if (wParam == VK_F6) {
|
||||
m_pendingInputEvents.push_back(MakeFocusEvent(UIInputEventType::FocusLost));
|
||||
return 0;
|
||||
}
|
||||
if (const std::int32_t keyCode = MapAssetFieldKey(static_cast<UINT>(wParam));
|
||||
keyCode != static_cast<std::int32_t>(KeyCode::None)) {
|
||||
m_pendingInputEvents.push_back(MakeKeyEvent(keyCode));
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_PAINT:
|
||||
RenderFrame();
|
||||
ValidateRect(hwnd, nullptr);
|
||||
return 0;
|
||||
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProcW(hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
HINSTANCE m_hInstance = nullptr;
|
||||
ATOM m_windowClassAtom = 0;
|
||||
NativeRenderer m_renderer = {};
|
||||
AutoScreenshotController m_autoScreenshot = {};
|
||||
std::filesystem::path m_captureRoot = {};
|
||||
XCEngine::Tests::EditorUI::EditorValidationShellPalette m_shellPalette = {};
|
||||
XCEngine::Tests::EditorUI::EditorValidationShellMetrics m_shellMetrics = {};
|
||||
UIEditorAssetFieldSpec m_spec = {};
|
||||
UIEditorAssetFieldInteractionState m_interactionState = {};
|
||||
UIEditorAssetFieldInteractionFrame m_frame = {};
|
||||
UIEditorAssetFieldPalette m_fieldPalette = {};
|
||||
XCEngine::UI::Editor::Widgets::UIEditorAssetFieldMetrics m_fieldMetrics = {};
|
||||
std::vector<UIInputEvent> m_pendingInputEvents = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_pressedAction = ActionId::None;
|
||||
std::string m_lastResult = {};
|
||||
std::size_t m_currentSampleIndex = 0u;
|
||||
std::size_t m_activateCount = 0u;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
|
||||
return ScenarioApp().Run(hInstance, nCmdShow);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorBoolFieldInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Widgets/UIEditorBoolField.h>
|
||||
#include <XCEditor/Fields/UIEditorBoolFieldInteraction.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorBoolField.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -47,7 +47,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorBoolField;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorBoolFieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorBoolFieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorBoolFieldSpec;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorBoolFieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | BoolField Basic";
|
||||
@@ -80,10 +79,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
@@ -371,14 +366,6 @@ private:
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/bool_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad =
|
||||
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
return true;
|
||||
@@ -407,7 +394,7 @@ private:
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
@@ -430,7 +417,7 @@ private:
|
||||
}
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics();
|
||||
m_frame = UpdateUIEditorBoolFieldInteraction(
|
||||
m_interactionState,
|
||||
m_value,
|
||||
@@ -531,7 +518,7 @@ private:
|
||||
|
||||
UIEditorBoolFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics();
|
||||
m_frame = UpdateUIEditorBoolFieldInteraction(
|
||||
m_interactionState,
|
||||
m_value,
|
||||
@@ -578,15 +565,15 @@ private:
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
RefreshFrame();
|
||||
|
||||
const UIEditorBoolFieldHitTarget currentHit =
|
||||
HitTestUIEditorBoolField(m_frame.layout, m_mousePosition);
|
||||
const auto boolMetrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(m_theme);
|
||||
const auto boolPalette = XCEngine::UI::Editor::ResolveUIEditorBoolFieldPalette(m_theme);
|
||||
const auto boolMetrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics();
|
||||
const auto boolPalette = XCEngine::UI::Editor::ResolveUIEditorBoolFieldPalette();
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorBoolFieldBasic");
|
||||
@@ -597,8 +584,8 @@ private:
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试在验证什么功能",
|
||||
"只验证 Editor BoolField 的基础交互契约,不涉及 PropertyGrid 或业务 Inspector。");
|
||||
"这个测试在验证什么功能?",
|
||||
"验证 Editor BoolField 的点击切换、键盘切换和状态同步,样式固定为 Editor 字段风格。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 点击 row 或 checkbox,检查 true / false 是否稳定切换。",
|
||||
@@ -606,7 +593,7 @@ private:
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 控件获得 focus 后按 Space / Enter,也必须能切换值。",
|
||||
"2. 控件获得 focus 后按 Space / Enter,也必须能够切换值。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
@@ -644,12 +631,12 @@ private:
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f),
|
||||
std::string("Focused: ") + (m_interactionState.fieldState.focused ? "开" : "关"),
|
||||
std::string("Focused: ") + (m_interactionState.fieldState.focused ? "是" : "否"),
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f),
|
||||
std::string("Active: ") + (m_interactionState.fieldState.active ? "开" : "关"),
|
||||
std::string("Active: ") + (m_interactionState.fieldState.active ? "是" : "否"),
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
@@ -674,11 +661,6 @@ private:
|
||||
captureSummary,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
@@ -686,7 +668,7 @@ private:
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"BoolField 预览",
|
||||
"这里只放一个 Unity 风格 BoolField。");
|
||||
"这里只放一个固定样式 BoolField,不混入其他业务控件。");
|
||||
UIEditorBoolFieldSpec previewSpec = m_spec;
|
||||
previewSpec.value = m_value;
|
||||
AppendUIEditorBoolField(
|
||||
@@ -715,12 +697,10 @@ private:
|
||||
bool m_value = false;
|
||||
UIEditorBoolFieldInteractionState m_interactionState = {};
|
||||
UIEditorBoolFieldInteractionFrame m_frame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_hoveredAction = ActionId::Reset;
|
||||
bool m_hasHoveredAction = false;
|
||||
std::string m_lastResult = {};
|
||||
std::string m_themeStatus = "fallback";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorColorFieldInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Widgets/UIEditorColorField.h>
|
||||
#include <XCEditor/Fields/UIEditorColorFieldInteraction.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorColorField.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -48,8 +48,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorColorField;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldSpec;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorColorFieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ColorField Basic";
|
||||
|
||||
@@ -81,11 +79,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool IsTruthyEnvironmentFlag(const char* name) {
|
||||
const char* value = std::getenv(name);
|
||||
if (value == nullptr || value[0] == '\0') {
|
||||
@@ -135,7 +128,7 @@ ScenarioLayout BuildScenarioLayout(
|
||||
layout.previewRect.x + 28.0f,
|
||||
layout.previewRect.y + 72.0f,
|
||||
360.0f,
|
||||
32.0f);
|
||||
22.0f);
|
||||
|
||||
const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f;
|
||||
const float buttonY = layout.controlRect.y + 32.0f;
|
||||
@@ -379,14 +372,6 @@ private:
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/color_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad =
|
||||
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
if (IsTruthyEnvironmentFlag("XCUI_COLOR_FIELD_OPEN_POPUP_ON_STARTUP")) {
|
||||
@@ -405,6 +390,7 @@ private:
|
||||
});
|
||||
m_lastResult = "已自动打开 ColorField 弹窗";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -431,7 +417,7 @@ private:
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
UIRect GetViewportRect() const {
|
||||
@@ -465,7 +451,7 @@ private:
|
||||
}
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics();
|
||||
m_frame = UpdateUIEditorColorFieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -558,7 +544,7 @@ private:
|
||||
|
||||
UIEditorColorFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics();
|
||||
m_frame = UpdateUIEditorColorFieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -594,6 +580,7 @@ private:
|
||||
case ActionId::Reset:
|
||||
ResetScenario();
|
||||
break;
|
||||
|
||||
case ActionId::Capture:
|
||||
m_autoScreenshot.RequestCapture("manual_button");
|
||||
m_lastResult = "已请求截图,输出到 captures/latest.png";
|
||||
@@ -602,16 +589,7 @@ private:
|
||||
}
|
||||
|
||||
std::string BuildColorSummary() const {
|
||||
char buffer[128] = {};
|
||||
std::snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"R %.3f G %.3f B %.3f A %.3f",
|
||||
m_spec.value.r,
|
||||
m_spec.value.g,
|
||||
m_spec.value.b,
|
||||
m_spec.value.a);
|
||||
return std::string(buffer);
|
||||
return XCEngine::UI::Editor::Widgets::FormatUIEditorColorFieldRgbaText(m_spec);
|
||||
}
|
||||
|
||||
void RenderFrame() {
|
||||
@@ -620,19 +598,20 @@ private:
|
||||
}
|
||||
|
||||
const UIRect viewportRect = GetViewportRect();
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const ScenarioLayout layout = BuildScenarioLayout(viewportRect.width, viewportRect.height, shellMetrics);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(
|
||||
viewportRect.width,
|
||||
viewportRect.height,
|
||||
shellMetrics);
|
||||
RefreshFrame();
|
||||
|
||||
const UIEditorColorFieldHitTarget currentHit =
|
||||
HitTestUIEditorColorField(
|
||||
m_frame.layout,
|
||||
m_interactionState.colorFieldState.popupOpen,
|
||||
m_mousePosition);
|
||||
|
||||
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(m_theme);
|
||||
const auto fieldPalette = XCEngine::UI::Editor::ResolveUIEditorColorFieldPalette(m_theme);
|
||||
const UIEditorColorFieldHitTarget currentHit = HitTestUIEditorColorField(
|
||||
m_frame.layout,
|
||||
m_interactionState.colorFieldState.popupOpen,
|
||||
m_mousePosition);
|
||||
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics();
|
||||
const auto fieldPalette = XCEngine::UI::Editor::ResolveUIEditorColorFieldPalette();
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorColorFieldBasic");
|
||||
@@ -643,31 +622,31 @@ private:
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试在验证什么功能",
|
||||
"只验证 Editor ColorField 的独立样式、弹窗结构和拾色交互,不混入 PropertyGrid。");
|
||||
"这个测试在验证什么功能?",
|
||||
"验证 Editor ColorField 的 swatch、popup、SV square、hue wheel、RGBA slider 和关闭行为。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 点击 swatch,检查 popup 是否在控件下方稳定展开。",
|
||||
"1. 点击 swatch,打开 popup。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 在 SV square 内拖拽,检查颜色是否连续变化。",
|
||||
"2. 拖动 SV square,检查颜色实时变化。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. 拖拽 hue wheel 与 R/G/B/A slider,检查颜色和透明度是否同步更新。",
|
||||
"3. 拖动 hue wheel 和 R / G / B / A slider,检查结果同步。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
|
||||
"4. 点击右上角 close 或点击控件外部,检查 popup 是否关闭。",
|
||||
"4. 点击 close 或外部区域,关闭 popup。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. 检查 Hexadecimal、数值框、handle、checkerboard 与截图路径是否同步。",
|
||||
"5. 左侧重点看 Hex、RGBA、Popup 和 Result。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
@@ -687,7 +666,7 @@ private:
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"状态摘要",
|
||||
"重点检查 hover / popup / result / hex / rgba。");
|
||||
"重点看 hover、popup、hex、rgba 和结果。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f),
|
||||
"Hover: " + DescribeHitTarget(currentHit),
|
||||
@@ -713,6 +692,7 @@ private:
|
||||
"Result: " + m_lastResult,
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
const std::string captureSummary =
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
? "截图排队中..."
|
||||
@@ -724,11 +704,6 @@ private:
|
||||
captureSummary,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
@@ -736,7 +711,7 @@ private:
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"ColorField 预览",
|
||||
"这里只放一个独立 ColorField,便于单点检查样式与交互。");
|
||||
"这里只放一个 ColorField,用来验证 Editor 基础字段的 popup 交互。");
|
||||
AppendUIEditorColorField(
|
||||
drawList,
|
||||
layout.fieldRect,
|
||||
@@ -763,12 +738,10 @@ private:
|
||||
UIEditorColorFieldSpec m_spec = {};
|
||||
UIEditorColorFieldInteractionState m_interactionState = {};
|
||||
UIEditorColorFieldInteractionFrame m_frame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_hoveredAction = ActionId::Reset;
|
||||
bool m_hasHoveredAction = false;
|
||||
std::string m_lastResult = {};
|
||||
std::string m_themeStatus = "fallback";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorCommandDispatcher.h>
|
||||
#include <XCEditor/Core/UIEditorMenuModel.h>
|
||||
#include <XCEditor/Core/UIEditorMenuSession.h>
|
||||
#include <XCEditor/Core/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
|
||||
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuModel.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuSession.h>
|
||||
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuPopup.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -767,7 +767,7 @@ void ScenarioApp::ResetScenario() {
|
||||
SetCustomResult(
|
||||
"等待操作",
|
||||
"Ready",
|
||||
"右键 Context Target 打开菜单;hover `Workspace Tools` 展开子菜单;Esc / 外部点击关闭。");
|
||||
"右键 Context Target 打开菜单,hover `Workspace Tools` 展开子菜单;Esc / 点击外部关闭。");
|
||||
}
|
||||
|
||||
void ScenarioApp::OnResize(UINT width, UINT height) {
|
||||
@@ -913,7 +913,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
|
||||
SetCustomResult(
|
||||
"Hover 收起更深层子菜单",
|
||||
"Dismissed",
|
||||
"鼠标移到普通菜单项后,更深层 child popup 已收起。closed: " +
|
||||
"鼠标移到普通菜单项后,更深层 child popup 已收起。Closed: " +
|
||||
JoinClosedPopupIds(mutation));
|
||||
dirty = true;
|
||||
}
|
||||
@@ -925,7 +925,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
|
||||
SetCustomResult(
|
||||
"Hover popup 空白区",
|
||||
"Dismissed",
|
||||
"鼠标停留在 popup 空白区后,更深层 child popup 已回收。closed: " +
|
||||
"鼠标停留在 popup 空白区后,更深层 child popup 已回收。Closed: " +
|
||||
JoinClosedPopupIds(mutation));
|
||||
dirty = true;
|
||||
}
|
||||
@@ -952,7 +952,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) {
|
||||
mutation.changed
|
||||
? "已展开 `" + hoveredItem->label +
|
||||
"` 子菜单。正常行为是 hover 也会直接展开。"
|
||||
: "子菜单已经处于打开状态。");
|
||||
: "子菜单已经处于展开状态。");
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
return;
|
||||
}
|
||||
@@ -983,7 +983,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) {
|
||||
SetCustomResult(
|
||||
"点击 popup 空白区",
|
||||
"Dismissed",
|
||||
"点击当前 popup 空白区后,仅更深层子菜单被关闭。closed: " +
|
||||
"点击当前 popup 空白区后,仅更深层子菜单被关闭。Closed: " +
|
||||
JoinClosedPopupIds(mutation));
|
||||
ClearHoverWhenMenuClosed();
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
@@ -998,7 +998,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) {
|
||||
"点击菜单外区域",
|
||||
mutation.changed ? "Dismissed" : "NoOp",
|
||||
mutation.changed
|
||||
? "点击外部区域后,整条菜单链已关闭。closed: " +
|
||||
? "点击外部区域后,整条菜单链已关闭。Closed: " +
|
||||
JoinClosedPopupIds(mutation)
|
||||
: "菜单链没有变化。");
|
||||
ClearHoverWhenMenuClosed();
|
||||
@@ -1015,7 +1015,7 @@ void ScenarioApp::HandleRightClick(float x, float y) {
|
||||
"右键外部区域",
|
||||
mutation.changed ? "Dismissed" : "NoOp",
|
||||
mutation.changed
|
||||
? "右键菜单外部区域后,整条菜单链已关闭。closed: " +
|
||||
? "右键菜单外部区域后,整条菜单链已关闭。Closed: " +
|
||||
JoinClosedPopupIds(mutation)
|
||||
: "菜单链没有变化。");
|
||||
ClearHoverWhenMenuClosed();
|
||||
@@ -1045,7 +1045,7 @@ void ScenarioApp::HandleKeyDown(UINT keyCode) {
|
||||
"Escape 关闭菜单",
|
||||
mutation.changed ? "Dismissed" : "NoOp",
|
||||
mutation.changed
|
||||
? "按下 Escape 后,topmost popup 已关闭。closed: " +
|
||||
? "按下 Escape 后,topmost popup 已关闭。Closed: " +
|
||||
JoinClosedPopupIds(mutation)
|
||||
: "当前没有可关闭的 popup。");
|
||||
ClearHoverWhenMenuClosed();
|
||||
@@ -1166,13 +1166,13 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
|
||||
DrawCard(
|
||||
drawList,
|
||||
m_headerRect,
|
||||
"测试功能:Editor ContextMenu 基础层",
|
||||
"这个测试验证什么功能?",
|
||||
"本场景只验证 root popup 锚点、submenu hover、outside/Esc dismiss、命令派发;不验证业务面板。");
|
||||
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 70.0f), "1. 在左侧 Context Target 内右键,root popup 必须贴近鼠标位置打开。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 92.0f), "2. hover `Workspace Tools`,右侧 child popup 必须立即弹出。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 114.0f), "3. 点击 Show/Activate/Hide/Reset,右侧 Details visible / active / activePanel 状态必须同步变化。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 136.0f), "4. 点击 popup 外部区域应整条关闭;如果 child popup 已打开,Esc 先关 topmost,再关 root。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 158.0f), "5. F12 截图;R 直接触发 Reset Workspace,便于确认命令派发仍正常。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 158.0f), "5. F12 截图,R 直接触发 Reset Workspace,便于确认命令派发仍然正常。", kTextPrimary, 13.0f);
|
||||
|
||||
DrawCard(drawList, m_targetRect, "Context Target", "只在这里接受右键打开 ContextMenu。");
|
||||
DrawCard(drawList, m_stateRect, "状态摘要", "重点看 popup chain、anchor 和 Details 状态。");
|
||||
@@ -1190,22 +1190,22 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
|
||||
drawList.AddRectOutline(targetSurface, kCardBorder, 1.0f, 10.0f);
|
||||
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 18.0f), "在这块区域右键打开 ContextMenu", kTextPrimary, 16.0f);
|
||||
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 46.0f), "当前 anchor: " + FormatAnchorPoint(m_contextAnchorPoint, m_hasContextAnchor), kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 68.0f), "Visible Panels: " + JoinVisiblePanelIds(workspace, session), kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 90.0f), "Details visible: " + std::string(detailsState != nullptr && detailsState->visible ? "true" : "false"), kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 112.0f), "Details active: " + std::string(workspace.activePanelId == "details" ? "true" : "false"), kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 68.0f), "可见面板: " + JoinVisiblePanelIds(workspace, session), kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 90.0f), "Details 可见: " + std::string(detailsState != nullptr && detailsState->visible ? "true" : "false"), kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 112.0f), "Details 激活: " + std::string(workspace.activePanelId == "details" ? "true" : "false"), kTextMuted, 12.0f);
|
||||
|
||||
const UIRect stateBox(m_stateRect.x + 18.0f, m_stateRect.y + 74.0f, m_stateRect.width - 36.0f, 220.0f);
|
||||
drawList.AddFilledRect(stateBox, kIndicatorBg, 10.0f);
|
||||
drawList.AddRectOutline(stateBox, kCardBorder, 1.0f, 10.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 14.0f), "Open root menu", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 14.0f), "已打开 root menu", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 34.0f), m_menuSession.HasOpenMenu() ? std::string(m_menuSession.GetOpenRootMenuId()) : "(none)", kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 62.0f), "Popup chain", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 62.0f), "Popup 链", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 82.0f), JoinPopupChainIds(m_menuSession), kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 110.0f), "Submenu path", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 110.0f), "Submenu 路径", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 130.0f), JoinSubmenuPathIds(m_menuSession), kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 158.0f), "Active panel", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 158.0f), "当前激活面板", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 178.0f), workspace.activePanelId.empty() ? "(none)" : workspace.activePanelId, kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 202.0f), "Menu validation: " + std::string(menuValidation.IsValid() ? "OK" : menuValidation.message), menuValidation.IsValid() ? kSuccess : kDanger, 12.0f);
|
||||
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 202.0f), "菜单验证: " + std::string(menuValidation.IsValid() ? "OK" : menuValidation.message), menuValidation.IsValid() ? kSuccess : kDanger, 12.0f);
|
||||
|
||||
const std::string captureSummary =
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorDockHostInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Widgets/UIEditorDockHost.h>
|
||||
#include <XCEditor/Shell/UIEditorDockHostInteraction.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Shell/UIEditorDockHost.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -584,15 +584,15 @@ private:
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("DockHostBasic");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
|
||||
|
||||
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 DockHost 基础交互 contract,不做 editor 业务面板。");
|
||||
DrawCard(drawList, m_introRect, "这个测试验证什么功能?", "只验证 DockHost 基础交互 contract,不做 editor 业务面板。");
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), "1. 验证 splitter drag 是否只通过 DockHostInteraction + WorkspaceController 完成。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), "2. 验证 tab activate / tab close / standalone panel activate / panel close。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), "3. 验证 active panel、visible panels、split ratio 是否统一收口到 controller。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 pointer capture / release 请求是否由 contract 明确返回。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作: 先拖中间 splitter,再点 Document A。", kTextWeak, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f), "然后关闭 Document B,最后点 Details 与 Console 的 X。", kTextWeak, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 pointer capture / release 请求是否通过 contract 明确返回。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作:先拖中间 splitter,再点 Document A。", kTextWeak, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f), "然后关闭 Document B,最后点 Details 或 Console 的 X。", kTextWeak, 11.0f);
|
||||
|
||||
DrawCard(drawList, m_controlsRect, "操作", "只保留当前场景必要按钮。");
|
||||
DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前场景必要按钮。");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
DrawButton(drawList, button);
|
||||
}
|
||||
@@ -605,25 +605,25 @@ private:
|
||||
};
|
||||
|
||||
addStateLine("Hover: " + DescribeHitTarget(m_interactionState.dockHostState.hoveredTarget), kTextPrimary, 11.0f);
|
||||
addStateLine("Result: " + m_lastStatus, m_lastColor);
|
||||
addStateLine("结果: " + m_lastStatus, m_lastColor);
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f);
|
||||
stateY += 34.0f;
|
||||
addStateLine("Active Panel: " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId));
|
||||
addStateLine("Visible Panels: " + JoinVisiblePanelIds(m_controller), kTextWeak, 11.0f);
|
||||
addStateLine("Focused: " + FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted);
|
||||
addStateLine("Capture: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted);
|
||||
addStateLine("当前面板: " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId));
|
||||
addStateLine("可见面板: " + JoinVisiblePanelIds(m_controller), kTextWeak, 11.0f);
|
||||
addStateLine("焦点: " + FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted);
|
||||
addStateLine("捕获: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted);
|
||||
addStateLine(
|
||||
"Dragging Splitter: " +
|
||||
"拖拽 Splitter: " +
|
||||
(m_interactionState.dockHostState.activeSplitterNodeId.empty()
|
||||
? std::string("(none)")
|
||||
: m_interactionState.dockHostState.activeSplitterNodeId),
|
||||
kTextWeak,
|
||||
11.0f);
|
||||
addStateLine("root-split ratio: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kTextWeak, 11.0f);
|
||||
addStateLine("根分割比例: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kTextWeak, 11.0f);
|
||||
if (m_controller.GetWorkspace().root.children.size() > 1u &&
|
||||
m_controller.GetWorkspace().root.children[1].children.size() > 1u) {
|
||||
addStateLine(
|
||||
"right-split ratio: " +
|
||||
"右侧分割比例: " +
|
||||
FormatFloat(m_controller.GetWorkspace().root.children[1].splitRatio),
|
||||
kTextWeak,
|
||||
11.0f);
|
||||
@@ -638,7 +638,7 @@ private:
|
||||
kTextWeak,
|
||||
11.0f);
|
||||
|
||||
DrawCard(drawList, m_previewRect, "Preview", "真实 DockHost 交互预览,只看基础层,不接旧 editor。");
|
||||
DrawCard(drawList, m_previewRect, "预览", "真实 DockHost 交互预览,只看基础层,不接旧 editor。");
|
||||
drawList.AddFilledRect(m_dockHostRect, kPreviewBg, 8.0f);
|
||||
AppendUIEditorDockHostBackground(drawList, m_cachedFrame.layout);
|
||||
AppendUIEditorDockHostForeground(drawList, m_cachedFrame.layout);
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorPanelRegistry.h>
|
||||
#include <XCEditor/Core/UIEditorShellCompose.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
|
||||
#include <XCEditor/Shell/UIEditorShellCompose.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include "../../shared/src/EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -41,6 +43,8 @@ using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationRequest;
|
||||
using XCEngine::UI::Editor::GetUIEditorWorkspaceCommandStatusName;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorShellComposeMetrics;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorShellComposePalette;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorShellComposeRequest;
|
||||
using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
|
||||
using XCEngine::UI::Editor::UIEditorPanelRegistry;
|
||||
@@ -61,19 +65,12 @@ using XCEngine::UI::Editor::Host::NativeRenderer;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorMenuBarItem;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSegment;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSlot;
|
||||
const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorShellComposeValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Shell Compose";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI <EFBFBD>༭??| <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
|
||||
|
||||
constexpr UIColor kWindowBg(0.10f, 0.10f, 0.10f, 1.0f);
|
||||
constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f);
|
||||
constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f);
|
||||
constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f);
|
||||
constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f);
|
||||
constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f);
|
||||
constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
constexpr UIColor kButtonOnBg(0.36f, 0.36f, 0.36f, 1.0f);
|
||||
constexpr UIColor kButtonBorder(0.46f, 0.46f, 0.46f, 1.0f);
|
||||
constexpr UIColor kSuccess(0.46f, 0.72f, 0.50f, 1.0f);
|
||||
constexpr UIColor kDanger(0.78f, 0.35f, 0.35f, 1.0f);
|
||||
|
||||
@@ -103,6 +100,7 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
@@ -134,27 +132,27 @@ void DrawCard(
|
||||
const UIRect& rect,
|
||||
std::string_view title,
|
||||
std::string_view subtitle = {}) {
|
||||
drawList.AddFilledRect(rect, kCardBg, 10.0f);
|
||||
drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f);
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f);
|
||||
drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius);
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kShellPalette.textPrimary, kShellMetrics.titleFontSize);
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kShellPalette.textMuted, kShellMetrics.bodyFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawButton(UIDrawList& drawList, const ButtonState& button) {
|
||||
drawList.AddFilledRect(button.rect, button.selected ? kButtonOnBg : kButtonBg, 8.0f);
|
||||
drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f);
|
||||
drawList.AddText(UIPoint(button.rect.x + 12.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f);
|
||||
drawList.AddFilledRect(button.rect, button.selected ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground, kShellMetrics.buttonRadius);
|
||||
drawList.AddRectOutline(button.rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.buttonRadius);
|
||||
drawList.AddText(UIPoint(button.rect.x + 12.0f, button.rect.y + 10.0f), button.label, kShellPalette.textPrimary, kShellMetrics.bodyFontSize);
|
||||
}
|
||||
|
||||
UIEditorPanelRegistry BuildPanelRegistry() {
|
||||
UIEditorPanelRegistry registry = {};
|
||||
registry.panels = {
|
||||
{ "hierarchy", "Hierarchy", UIEditorPanelPresentationKind::Placeholder, true, true, false },
|
||||
{ "scene", "Scene", UIEditorPanelPresentationKind::ViewportShell, false, true, false },
|
||||
{ "document", "Document", UIEditorPanelPresentationKind::Placeholder, true, true, true },
|
||||
{ "inspector", "Inspector", UIEditorPanelPresentationKind::Placeholder, true, true, true }
|
||||
{ "hierarchy", "层级", UIEditorPanelPresentationKind::Placeholder, true, true, false },
|
||||
{ "scene", "场景", UIEditorPanelPresentationKind::ViewportShell, false, true, false },
|
||||
{ "document", "文档", UIEditorPanelPresentationKind::Placeholder, true, true, true },
|
||||
{ "inspector", "检视器", UIEditorPanelPresentationKind::Placeholder, true, true, true }
|
||||
};
|
||||
return registry;
|
||||
}
|
||||
@@ -165,7 +163,7 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
"root-left-main",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.23f,
|
||||
BuildUIEditorWorkspacePanel("hierarchy-node", "hierarchy", "Hierarchy", true),
|
||||
BuildUIEditorWorkspacePanel("hierarchy-node", "hierarchy", "层级", true),
|
||||
BuildUIEditorWorkspaceSplit(
|
||||
"main-center-right",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
@@ -173,11 +171,11 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
BuildUIEditorWorkspaceTabStack(
|
||||
"center-tabs",
|
||||
{
|
||||
BuildUIEditorWorkspacePanel("scene-node", "scene", "Scene"),
|
||||
BuildUIEditorWorkspacePanel("document-node", "document", "Document", true)
|
||||
BuildUIEditorWorkspacePanel("scene-node", "scene", "场景"),
|
||||
BuildUIEditorWorkspacePanel("document-node", "document", "文档", true)
|
||||
},
|
||||
0u),
|
||||
BuildUIEditorWorkspacePanel("inspector-node", "inspector", "Inspector", true)));
|
||||
BuildUIEditorWorkspacePanel("inspector-node", "inspector", "检视器", true)));
|
||||
workspace.activePanelId = "scene";
|
||||
return workspace;
|
||||
}
|
||||
@@ -318,7 +316,7 @@ private:
|
||||
m_showTopBar = true;
|
||||
m_showBottomBar = true;
|
||||
m_textureEnabled = true;
|
||||
m_lastResult = "Ready";
|
||||
m_lastResult = "就绪";
|
||||
}
|
||||
|
||||
void UpdateLayoutForCurrentWindow() {
|
||||
@@ -350,13 +348,13 @@ private:
|
||||
const float widthAvailable = m_controlsRect.width - 32.0f;
|
||||
const bool sceneSelected = GetSelectedTabId() == "scene";
|
||||
m_buttons = {
|
||||
{ ActionId::ActivateScene, "切到 Scene", UIRect(left, top, widthAvailable, buttonHeight), sceneSelected },
|
||||
{ ActionId::ActivateDocument, "切到 Document", UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight), !sceneSelected },
|
||||
{ ActionId::ToggleTopBar, std::string("TopBar: ") + (m_showTopBar ? "开" : "关"), UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight), m_showTopBar },
|
||||
{ ActionId::ToggleBottomBar, std::string("BottomBar: ") + (m_showBottomBar ? "开" : "关"), UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight), m_showBottomBar },
|
||||
{ ActionId::ToggleTexture, std::string("Texture: ") + (m_textureEnabled ? "开" : "关"), UIRect(left, top + (buttonHeight + gap) * 4.0f, widthAvailable, buttonHeight), m_textureEnabled },
|
||||
{ ActionId::ActivateScene, "Scene", UIRect(left, top, widthAvailable, buttonHeight), sceneSelected },
|
||||
{ ActionId::ActivateDocument, "Document", UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight), !sceneSelected },
|
||||
{ ActionId::ToggleTopBar, std::string("Top Bar: ") + (m_showTopBar ? "on" : "off"), UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight), m_showTopBar },
|
||||
{ ActionId::ToggleBottomBar, std::string("Bottom Bar: ") + (m_showBottomBar ? "on" : "off"), UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight), m_showBottomBar },
|
||||
{ ActionId::ToggleTexture, std::string("Texture: ") + (m_textureEnabled ? "on" : "off"), UIRect(left, top + (buttonHeight + gap) * 4.0f, widthAvailable, buttonHeight), m_textureEnabled },
|
||||
{ ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 5.0f, widthAvailable, buttonHeight), false },
|
||||
{ ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 6.0f, widthAvailable, buttonHeight), false }
|
||||
{ ActionId::Capture, "Capture", UIRect(left, top + (buttonHeight + gap) * 6.0f, widthAvailable, buttonHeight), false }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -369,7 +367,7 @@ private:
|
||||
};
|
||||
model.statusSegments = {
|
||||
UIEditorStatusBarSegment{ "mode", GetSelectedTabId() == "scene" ? "Scene" : "Document", UIEditorStatusBarSlot::Leading, {}, true, true, 86.0f },
|
||||
UIEditorStatusBarSegment{ "chrome", std::string("Top ") + (m_showTopBar ? "On" : "Off"), UIEditorStatusBarSlot::Leading, {}, true, false, 82.0f },
|
||||
UIEditorStatusBarSegment{ "chrome", std::string("Top ") + (m_showTopBar ? "on" : "off"), UIEditorStatusBarSlot::Leading, {}, true, false, 82.0f },
|
||||
UIEditorStatusBarSegment{ "branch", m_textureEnabled ? "Texture" : "Fallback", UIEditorStatusBarSlot::Trailing, {}, true, true, 96.0f }
|
||||
};
|
||||
|
||||
@@ -377,7 +375,7 @@ private:
|
||||
presentation.panelId = "scene";
|
||||
presentation.kind = UIEditorPanelPresentationKind::ViewportShell;
|
||||
presentation.viewportShellModel.spec.chrome.title = "Scene";
|
||||
presentation.viewportShellModel.spec.chrome.subtitle = "Editor Shell 基础层";
|
||||
presentation.viewportShellModel.spec.chrome.subtitle = "Shell compose validation";
|
||||
presentation.viewportShellModel.spec.chrome.showTopBar = m_showTopBar;
|
||||
presentation.viewportShellModel.spec.chrome.showBottomBar = m_showBottomBar;
|
||||
presentation.viewportShellModel.spec.chrome.topBarHeight = 40.0f;
|
||||
@@ -387,16 +385,16 @@ private:
|
||||
};
|
||||
presentation.viewportShellModel.spec.statusSegments = {
|
||||
{ "view", "Shell", UIEditorStatusBarSlot::Leading, {}, true, true, 64.0f },
|
||||
{ "branch", m_textureEnabled ? "Texture" : "Fallback", UIEditorStatusBarSlot::Trailing, {}, true, false, 96.0f }
|
||||
{ "branch", m_textureEnabled ? "纹理" : "回退", UIEditorStatusBarSlot::Trailing, {}, true, false, 96.0f }
|
||||
};
|
||||
if (m_textureEnabled) {
|
||||
presentation.viewportShellModel.frame.hasTexture = true;
|
||||
presentation.viewportShellModel.frame.texture = { 1u, 1280u, 720u };
|
||||
presentation.viewportShellModel.frame.presentedSize = UISize(1280.0f, 720.0f);
|
||||
presentation.viewportShellModel.frame.statusText = "Fake viewport frame";
|
||||
presentation.viewportShellModel.frame.statusText = "Simulated viewport frame";
|
||||
} else {
|
||||
presentation.viewportShellModel.frame.hasTexture = false;
|
||||
presentation.viewportShellModel.frame.statusText = "这里只验证 Editor shell compose,不接 Scene/Game 业务。";
|
||||
presentation.viewportShellModel.frame.statusText = "Simulated viewport frame";
|
||||
}
|
||||
model.workspacePresentations = { presentation };
|
||||
return model;
|
||||
@@ -411,6 +409,7 @@ private:
|
||||
|
||||
void UpdateShellFrame() {
|
||||
const UIEditorShellComposeModel model = BuildShellModel();
|
||||
const auto metrics = ResolveUIEditorShellComposeMetrics();
|
||||
m_shellRequest = ResolveUIEditorShellComposeRequest(
|
||||
m_shellRect,
|
||||
m_controller.GetPanelRegistry(),
|
||||
@@ -418,7 +417,8 @@ private:
|
||||
m_controller.GetSession(),
|
||||
model,
|
||||
{},
|
||||
m_shellState);
|
||||
m_shellState,
|
||||
metrics);
|
||||
m_shellFrame = UpdateUIEditorShellCompose(
|
||||
m_shellState,
|
||||
m_shellRect,
|
||||
@@ -426,7 +426,9 @@ private:
|
||||
m_controller.GetWorkspace(),
|
||||
m_controller.GetSession(),
|
||||
model,
|
||||
{});
|
||||
{},
|
||||
{},
|
||||
metrics);
|
||||
m_cachedModel = model;
|
||||
}
|
||||
|
||||
@@ -445,30 +447,30 @@ private:
|
||||
void ExecuteAction(ActionId action) {
|
||||
switch (action) {
|
||||
case ActionId::ActivateScene:
|
||||
DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "scene", "Activate Scene");
|
||||
DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "scene", "切到场景");
|
||||
break;
|
||||
case ActionId::ActivateDocument:
|
||||
DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "document", "Activate Document");
|
||||
DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "document", "切到文档");
|
||||
break;
|
||||
case ActionId::ToggleTopBar:
|
||||
m_showTopBar = !m_showTopBar;
|
||||
m_lastResult = m_showTopBar ? "TopBar 已打开" : "TopBar 已关闭";
|
||||
m_lastResult = m_showTopBar ? "Top bar enabled" : "Top bar disabled";
|
||||
break;
|
||||
case ActionId::ToggleBottomBar:
|
||||
m_showBottomBar = !m_showBottomBar;
|
||||
m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关闭";
|
||||
m_lastResult = m_showBottomBar ? "Bottom bar enabled" : "Bottom bar disabled";
|
||||
break;
|
||||
case ActionId::ToggleTexture:
|
||||
m_textureEnabled = !m_textureEnabled;
|
||||
m_lastResult = m_textureEnabled ? "切到 Texture 分支" : "切到 Fallback 分支";
|
||||
m_lastResult = m_textureEnabled ? "Texture branch enabled" : "Fallback branch enabled";
|
||||
break;
|
||||
case ActionId::Reset:
|
||||
ResetScenario();
|
||||
m_lastResult = "状态已重置";
|
||||
m_lastResult = "Scenario reset";
|
||||
break;
|
||||
case ActionId::Capture:
|
||||
m_autoScreenshot.RequestCapture("manual_button");
|
||||
m_lastResult = "截图已排队";
|
||||
m_lastResult = "Capture queued";
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
UpdateWindow(m_hwnd);
|
||||
break;
|
||||
@@ -504,53 +506,46 @@ private:
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorShellCompose");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
|
||||
|
||||
DrawCard(drawList, m_introRect, "测试功能:Editor 根壳层 compose", "只验证 MenuBar / Workspace / StatusBar 三段根壳组合。");
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), "检查 1:顶部 MenuBar、中间 WorkspaceCompose、底部 StatusBar 必须各自占带,不能互相挤压或重叠。", kTextMuted, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f), "检查 2:切换 Scene / Document 时,只影响中间 Workspace body presentation,不应破坏顶栏和底栏。", kTextMuted, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f), "检查 3:切换 TopBar / BottomBar / Texture 时,Request Size 必须同步,但 MenuBar / StatusBar 位置保持稳定。", kTextMuted, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f), "这一层暂时不验证 menu popup 或 shortcut,只验证 Editor 根框架 compose。", kTextWeak, 11.0f);
|
||||
|
||||
DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前这个根壳层需要检查的 7 个控件。");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground);
|
||||
DrawCard(drawList, m_introRect, "Validation Goal", "Verify root shell composition only.");
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), "1. Menu bar, workspace, and status bar must compose into one stable shell.", kShellPalette.textMuted, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f), "2. Scene should host the viewport shell, while Document should fall back to a placeholder.", kShellPalette.textMuted, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f), "3. Top bar, bottom bar, and texture toggles must update layout and branch selection together.", kShellPalette.textMuted, 11.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f), "4. This scene validates shell composition only. No business panels are involved.", kShellPalette.textWeak, 11.0f);
|
||||
DrawCard(drawList, m_controlsRect, "Actions", "Only controls required for this validation are exposed.");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
DrawButton(drawList, button);
|
||||
}
|
||||
|
||||
DrawCard(drawList, m_stateRect, "状态", "左侧直接回显根壳层三段布局与中间 workspace request。");
|
||||
DrawCard(drawList, m_stateRect, "State", "Inspect current shell layout, request size, and validation result.");
|
||||
float stateY = m_stateRect.y + 66.0f;
|
||||
auto addStateLine = [&](std::string text, const UIColor& color, float fontSize = 12.0f) {
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize);
|
||||
stateY += 18.0f;
|
||||
};
|
||||
addStateLine("Active Panel: " + m_controller.GetWorkspace().activePanelId, kTextPrimary);
|
||||
addStateLine("Selected Tab: " + GetSelectedTabId(), kTextPrimary);
|
||||
addStateLine("Selected Presentation: " + selectedPresentation, kTextPrimary);
|
||||
addStateLine("MenuBar Rect: " + FormatRect(m_shellFrame.layout.menuBarRect), kTextMuted, 11.0f);
|
||||
addStateLine("Workspace Rect: " + FormatRect(m_shellFrame.layout.workspaceRect), kTextMuted, 11.0f);
|
||||
addStateLine("StatusBar Rect: " + FormatRect(m_shellFrame.layout.statusBarRect), kTextMuted, 11.0f);
|
||||
addStateLine("Requested viewport size: " + (viewportRequest != nullptr ? FormatSize(viewportRequest->viewportShellRequest.requestedViewportSize) : std::string("n/a")), kShellPalette.textPrimary);
|
||||
addStateLine("Result: " + m_lastResult, kShellPalette.textMuted);
|
||||
addStateLine(
|
||||
"Request Size: " +
|
||||
(viewportRequest != nullptr
|
||||
? FormatSize(viewportRequest->viewportShellRequest.requestedViewportSize)
|
||||
: std::string("n/a")),
|
||||
kTextPrimary);
|
||||
addStateLine("Result: " + m_lastResult, kTextMuted);
|
||||
addStateLine(
|
||||
validation.IsValid() ? "Workspace Validation: OK" : "Workspace Validation: " + validation.message,
|
||||
validation.IsValid() ? "工作区校验:正常" : "工作区校验:" + validation.message,
|
||||
validation.IsValid() ? kSuccess : kDanger,
|
||||
11.0f);
|
||||
|
||||
const std::string captureSummary =
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
? "截图排队中..."
|
||||
? "Capture queued..."
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("截图:F12 或按钮 -> editor_shell_compose/captures/")
|
||||
? std::string("F12 or the Capture button -> editor_shell_compose/captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary());
|
||||
addStateLine(captureSummary, kTextWeak, 11.0f);
|
||||
|
||||
DrawCard(drawList, m_previewRect, "Preview", "这里只有 Editor 根壳层 compose,不混入业务面板。");
|
||||
AppendUIEditorShellCompose(drawList, m_shellFrame, m_cachedModel, m_shellState);
|
||||
addStateLine(captureSummary, kShellPalette.textWeak, 11.0f);
|
||||
DrawCard(drawList, m_previewRect, "Preview", "Live UIEditorShellCompose preview.");
|
||||
const auto palette = ResolveUIEditorShellComposePalette();
|
||||
const auto metrics = ResolveUIEditorShellComposeMetrics();
|
||||
AppendUIEditorShellCompose(
|
||||
drawList,
|
||||
m_shellFrame,
|
||||
m_cachedModel,
|
||||
m_shellState,
|
||||
palette,
|
||||
metrics);
|
||||
|
||||
const bool framePresented = m_renderer.Render(drawData);
|
||||
m_autoScreenshot.CaptureIfRequested(
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "Core/EditorShellAsset.h"
|
||||
#include "Shell/EditorShellAsset.h"
|
||||
|
||||
#include <XCEditor/Core/UIEditorCommandDispatcher.h>
|
||||
#include <XCEditor/Core/UIEditorMenuModel.h>
|
||||
#include <XCEditor/Core/UIEditorShellInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuModel.h>
|
||||
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
|
||||
#include "../../shared/src/EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -19,6 +21,8 @@
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -54,6 +58,8 @@ using XCEngine::UI::Editor::EditorShellAsset;
|
||||
using XCEngine::UI::Editor::EditorShellAssetValidationResult;
|
||||
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame;
|
||||
using XCEngine::UI::Editor::GetUIEditorCommandDispatchStatusName;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorShellInteractionMetrics;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorShellInteractionPalette;
|
||||
using XCEngine::UI::Editor::UpdateUIEditorShellInteraction;
|
||||
using XCEngine::UI::Editor::UIEditorCommandDispatchResult;
|
||||
using XCEngine::UI::Editor::UIEditorCommandDispatcher;
|
||||
@@ -85,19 +91,12 @@ using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSlot;
|
||||
using XCEngine::UI::Widgets::UIPopupDismissReason;
|
||||
using XCEngine::UI::Editor::Host::AutoScreenshotController;
|
||||
using XCEngine::UI::Editor::Host::NativeRenderer;
|
||||
const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorShellInteractionValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Shell Interaction";
|
||||
|
||||
constexpr UIColor kWindowBg(0.10f, 0.10f, 0.10f, 1.0f);
|
||||
constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f);
|
||||
constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f);
|
||||
constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f);
|
||||
constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f);
|
||||
constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f);
|
||||
constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
constexpr UIColor kButtonHover(0.32f, 0.32f, 0.32f, 1.0f);
|
||||
constexpr UIColor kButtonBorder(0.46f, 0.46f, 0.46f, 1.0f);
|
||||
constexpr UIColor kSuccess(0.46f, 0.72f, 0.50f, 1.0f);
|
||||
constexpr UIColor kWarning(0.82f, 0.67f, 0.35f, 1.0f);
|
||||
constexpr UIColor kDanger(0.78f, 0.35f, 0.35f, 1.0f);
|
||||
@@ -122,6 +121,24 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
bool IsAutoCaptureOnStartupEnabled() {
|
||||
const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP");
|
||||
if (value == nullptr || value[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string normalized = value;
|
||||
for (char& character : normalized) {
|
||||
character = static_cast<char>(std::tolower(static_cast<unsigned char>(character)));
|
||||
}
|
||||
|
||||
return normalized != "0" &&
|
||||
normalized != "false" &&
|
||||
normalized != "off" &&
|
||||
normalized != "no";
|
||||
}
|
||||
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
@@ -248,18 +265,18 @@ void DrawCard(
|
||||
const UIRect& rect,
|
||||
std::string_view title,
|
||||
std::string_view subtitle = {}) {
|
||||
drawList.AddFilledRect(rect, kCardBg, 10.0f);
|
||||
drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f);
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f);
|
||||
drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius);
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kShellPalette.textPrimary, kShellMetrics.titleFontSize);
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kShellPalette.textMuted, kShellMetrics.bodyFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawButton(UIDrawList& drawList, const ButtonState& button) {
|
||||
drawList.AddFilledRect(button.rect, button.hovered ? kButtonHover : kButtonBg, 8.0f);
|
||||
drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f);
|
||||
drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f);
|
||||
drawList.AddFilledRect(button.rect, button.hovered ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground, kShellMetrics.buttonRadius);
|
||||
drawList.AddRectOutline(button.rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.buttonRadius);
|
||||
drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kShellPalette.textPrimary, kShellMetrics.bodyFontSize);
|
||||
}
|
||||
|
||||
std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) {
|
||||
@@ -433,7 +450,7 @@ EditorShellAsset BuildScenarioShellAsset() {
|
||||
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
|
||||
presentation.viewportShellModel.frame.hasTexture = false;
|
||||
presentation.viewportShellModel.frame.statusText =
|
||||
"这里只验证 Editor 根壳交互,不接旧 editor 业务面板。";
|
||||
"该场景只验证 Editor Shell 交互契约,不接 editor 业务面板。";
|
||||
}
|
||||
asset.shellDefinition.workspacePresentations.push_back(std::move(presentation));
|
||||
}
|
||||
@@ -441,7 +458,6 @@ EditorShellAsset BuildScenarioShellAsset() {
|
||||
asset.shortcutAsset.commandRegistry = BuildCommandRegistry();
|
||||
return asset;
|
||||
}
|
||||
|
||||
class ScenarioApp {
|
||||
public:
|
||||
int Run(HINSTANCE hInstance, int nCmdShow);
|
||||
@@ -485,7 +501,7 @@ private:
|
||||
bool m_trackingMouseLeave = false;
|
||||
std::string m_lastStatus = {};
|
||||
std::string m_lastMessage = {};
|
||||
UIColor m_lastColor = kTextMuted;
|
||||
UIColor m_lastColor = kShellPalette.textMuted;
|
||||
};
|
||||
|
||||
int ScenarioApp::Run(HINSTANCE hInstance, int nCmdShow) {
|
||||
@@ -666,6 +682,11 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
if (IsAutoCaptureOnStartupEnabled()) {
|
||||
m_lastStatus = "Capture";
|
||||
m_lastMessage = "启动自动截图已排队。先核对首帧,再检查 root switch、child popup、dismiss 与 workspace suppression。";
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -708,10 +729,9 @@ void ScenarioApp::ResetScenario() {
|
||||
return;
|
||||
}
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage = "等待交互。这里只验证 Editor 根壳统一交互 contract,不接旧 editor 业务。";
|
||||
m_lastMessage = "等待交互。重点检查根菜单切换、hover 子菜单、关闭链路与 workspace 抑制;设 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可抓启动首帧。";
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
|
||||
void ScenarioApp::UpdateLayout() {
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
@@ -734,7 +754,6 @@ void ScenarioApp::UpdateLayout() {
|
||||
{ ActionId::Capture, "截图(F12)", UIRect(left + buttonWidth + 12.0f, top, buttonWidth, 36.0f), false }
|
||||
};
|
||||
}
|
||||
|
||||
void ScenarioApp::HandleMouseMove(float x, float y) {
|
||||
UpdateLayout();
|
||||
for (ButtonState& button : m_buttons) {
|
||||
@@ -775,17 +794,17 @@ void ScenarioApp::ExecuteAction(ActionId action) {
|
||||
if (action == ActionId::Reset) {
|
||||
ResetScenario();
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage = "场景状态已重置。请重新检查 root open / child popup / dismiss 行为。";
|
||||
m_lastMessage = "已重置场景。请重新检查 root open / child popup / dismiss 行为。";
|
||||
m_lastColor = kWarning;
|
||||
return;
|
||||
}
|
||||
|
||||
m_autoScreenshot.RequestCapture("manual_button");
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage = "截图已排队,输出到 tests/UI/Editor/integration/shell/editor_shell_interaction/captures/。";
|
||||
m_lastMessage =
|
||||
"截图请求已排队。检查状态卡片中的 Output/latest.png。";
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
|
||||
bool ScenarioApp::HasInteractiveCaptureState() const {
|
||||
if (m_interactionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) {
|
||||
return true;
|
||||
@@ -833,7 +852,7 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res
|
||||
|
||||
if (result.commandTriggered) {
|
||||
m_lastStatus = "Triggered";
|
||||
m_lastMessage = "命令已命中,但本帧没有完成 dispatch。请检查 shell services / asset contract。";
|
||||
m_lastMessage = "命令已触发但尚未派发,请检查 dispatch 链路。";
|
||||
m_lastColor = kWarning;
|
||||
return;
|
||||
}
|
||||
@@ -842,19 +861,19 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res
|
||||
if (!result.itemId.empty() && !result.menuMutation.openedPopupId.empty()) {
|
||||
m_lastStatus = "Changed";
|
||||
m_lastMessage =
|
||||
"已展开子菜单 `" + result.itemId + "`。请检查 child popup 是否在 hover 时直接弹出。";
|
||||
"`" + result.itemId + "` 已展开 child popup,hover 应直接弹出。";
|
||||
m_lastColor = kSuccess;
|
||||
} else if (!result.menuId.empty() && !result.menuMutation.openedPopupId.empty()) {
|
||||
m_lastStatus = "Changed";
|
||||
m_lastMessage =
|
||||
"当前根菜单为 `" + result.menuId + "`。请检查 root popup 是否在打开态下可直接切换。";
|
||||
"当前根菜单切换为 `" + result.menuId + "`,root popup 应立即切换。";
|
||||
m_lastColor = kSuccess;
|
||||
} else {
|
||||
m_lastStatus = "Dismissed";
|
||||
m_lastMessage =
|
||||
"菜单链已收起。DismissReason = " +
|
||||
FormatDismissReason(result.menuMutation.dismissReason) +
|
||||
"。请检查 outside pointer down / Esc / focus loss 的收束是否正确。";
|
||||
";请检查 outside pointer down / Esc / focus loss。";
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
return;
|
||||
@@ -888,41 +907,41 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res
|
||||
|
||||
if (result.requestPointerCapture) {
|
||||
m_lastStatus = "Capture";
|
||||
m_lastMessage = "宿主已收到 root shell 返回的 pointer capture 请求。";
|
||||
m_lastMessage = "控件请求了 pointer capture。";
|
||||
m_lastColor = kSuccess;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.releasePointerCapture) {
|
||||
m_lastStatus = "Release";
|
||||
m_lastMessage = "宿主已执行 root shell 返回的 pointer release。";
|
||||
m_lastMessage = "控件释放了 pointer capture。";
|
||||
m_lastColor = kWarning;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.consumed) {
|
||||
m_lastStatus = "NoOp";
|
||||
m_lastMessage = "这次输入被根壳交互层消费,但没有触发额外状态变化。";
|
||||
m_lastMessage = "输入已被消费,但没有产生更高层状态变化。";
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
}
|
||||
|
||||
void ScenarioApp::SetDispatchResult(const UIEditorCommandDispatchResult& result) {
|
||||
m_lastStatus = std::string(GetUIEditorCommandDispatchStatusName(result.status));
|
||||
m_lastMessage = result.message.empty() ? std::string("命令派发完成。") : result.message;
|
||||
m_lastMessage = result.message.empty() ? std::string("命令已派发。") : result.message;
|
||||
m_lastColor = m_lastStatus == "Dispatched" ? kSuccess : kDanger;
|
||||
}
|
||||
|
||||
void ScenarioApp::RenderFrame() {
|
||||
UpdateLayout();
|
||||
const UIEditorShellInteractionDefinition definition = BuildInteractionDefinition();
|
||||
const auto metrics = ResolveUIEditorShellInteractionMetrics();
|
||||
m_cachedFrame = UpdateUIEditorShellInteraction(
|
||||
m_interactionState,
|
||||
m_controller,
|
||||
m_shellRect,
|
||||
definition,
|
||||
m_pendingInputEvents,
|
||||
m_shellServices);
|
||||
m_shellServices,
|
||||
metrics);
|
||||
m_pendingInputEvents.clear();
|
||||
ApplyHostCaptureRequests(m_cachedFrame.result);
|
||||
SetInteractionResult(m_cachedFrame.result);
|
||||
@@ -939,71 +958,73 @@ void ScenarioApp::RenderFrame() {
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorShellInteraction");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground);
|
||||
|
||||
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 Editor 根壳统一交互 contract,不做业务面板。");
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 70.0f), "1. 验证 MenuBar 的 root open / root switch 行为是否统一稳定。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), "2. 验证 hover 子菜单时,child popup 是否直接展开,不需要额外点击。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 114.0f), "3. 验证 outside pointer down / Esc / focus loss 是否能正确收起 popup chain。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 验证菜单打开时会屏蔽 workspace 输入;菜单关闭后,workspace 交互立即恢复。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证菜单命令会在 root shell 内直接 dispatch,宿主不再二次派发。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 182.0f), "建议操作:点击 File,hover `Workspace Tools`,点预览外空白处,再点 `Document` 或拖 splitter。", kTextWeak, 11.0f);
|
||||
DrawCard(
|
||||
drawList,
|
||||
m_introRect,
|
||||
"这个测试验证什么?",
|
||||
"只验证 Editor Shell 统一交互 contract,不接 editor 业务面板。");
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 70.0f), "1. 验证 MenuBar 的 root open / root switch 行为是否统一。", kShellPalette.textPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), "2. 验证 hover 子菜单项时,child popup 是否直接弹出。", kShellPalette.textPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 114.0f), "3. 验证 outside pointer down / Esc / focus loss 是否正确关闭 popup chain。", kShellPalette.textPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 验证菜单打开时是否抑制 workspace 输入;菜单关闭后是否恢复。", kShellPalette.textPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证命令是否由 root shell 直接 dispatch,不依赖 editor 业务层。", kShellPalette.textPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 182.0f), "建议操作:点击 File,hover `Workspace Tools`;再切换 `Document` 标签并拖动 splitter。设 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可自动抓首帧。", kShellPalette.textWeak, 11.0f);
|
||||
|
||||
DrawCard(drawList, m_controlsRect, "操作", "只保留这个场景必要的控制。");
|
||||
DrawCard(drawList, m_controlsRect, "操作", "只放当前批次需要检查的交互。");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
DrawButton(drawList, button);
|
||||
}
|
||||
|
||||
DrawCard(drawList, m_stateRect, "状态", "重点检查根壳 contract 当前状态。");
|
||||
DrawCard(drawList, m_stateRect, "状态", "直接观察当前 contract 的状态。");
|
||||
float stateY = m_stateRect.y + 66.0f;
|
||||
auto addStateLine = [&](std::string label, std::string value, const UIColor& color, float fontSize = 12.0f) {
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(label) + ": " + std::move(value), color, fontSize);
|
||||
stateY += 20.0f;
|
||||
};
|
||||
|
||||
addStateLine("Open Root", m_cachedFrame.openRootMenuId.empty() ? "(none)" : m_cachedFrame.openRootMenuId, kTextPrimary);
|
||||
addStateLine("Popup Chain", JoinPopupChainIds(m_interactionState), kTextPrimary, 11.0f);
|
||||
addStateLine("Submenu Path", JoinSubmenuPathIds(m_interactionState), kTextPrimary, 11.0f);
|
||||
addStateLine("根菜单", m_cachedFrame.openRootMenuId.empty() ? "(none)" : m_cachedFrame.openRootMenuId, kShellPalette.textPrimary);
|
||||
addStateLine("Popup 链", JoinPopupChainIds(m_interactionState), kShellPalette.textPrimary, 11.0f);
|
||||
addStateLine("子菜单路径", JoinSubmenuPathIds(m_interactionState), kShellPalette.textPrimary, 11.0f);
|
||||
addStateLine("资源校验", m_assetValidation.IsValid() ? "OK" : m_assetValidation.message, m_assetValidation.IsValid() ? kSuccess : kDanger, 11.0f);
|
||||
addStateLine("当前呈现", selectedPresentation, kShellPalette.textPrimary, 11.0f);
|
||||
addStateLine("当前面板", m_controller.GetWorkspace().activePanelId.empty() ? "(none)" : m_controller.GetWorkspace().activePanelId, kShellPalette.textPrimary, 11.0f);
|
||||
addStateLine("焦点", FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kShellPalette.textMuted);
|
||||
addStateLine("菜单模态", FormatBool(m_cachedFrame.result.menuModal), m_cachedFrame.result.menuModal ? kSuccess : kShellPalette.textMuted, 11.0f);
|
||||
addStateLine("Workspace 抑制", FormatBool(m_cachedFrame.result.workspaceInputSuppressed), m_cachedFrame.result.workspaceInputSuppressed ? kWarning : kShellPalette.textMuted, 11.0f);
|
||||
addStateLine(
|
||||
"Asset Validation",
|
||||
m_assetValidation.IsValid() ? "OK" : m_assetValidation.message,
|
||||
m_assetValidation.IsValid() ? kSuccess : kDanger,
|
||||
11.0f);
|
||||
addStateLine("Selected Presentation", selectedPresentation, kTextPrimary, 11.0f);
|
||||
addStateLine("Active Panel", m_controller.GetWorkspace().activePanelId.empty() ? "(none)" : m_controller.GetWorkspace().activePanelId, kTextPrimary, 11.0f);
|
||||
addStateLine("Focused", FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted);
|
||||
addStateLine("Menu Modal", FormatBool(m_cachedFrame.result.menuModal), m_cachedFrame.result.menuModal ? kSuccess : kTextMuted, 11.0f);
|
||||
addStateLine(
|
||||
"Workspace Suppressed",
|
||||
FormatBool(m_cachedFrame.result.workspaceInputSuppressed),
|
||||
m_cachedFrame.result.workspaceInputSuppressed ? kWarning : kTextMuted,
|
||||
11.0f);
|
||||
addStateLine(
|
||||
"Command Dispatch",
|
||||
"命令派发",
|
||||
m_cachedFrame.result.commandDispatched
|
||||
? std::string(GetUIEditorCommandDispatchStatusName(m_cachedFrame.result.commandDispatchResult.status))
|
||||
: std::string("(none)"),
|
||||
m_cachedFrame.result.commandDispatched && m_cachedFrame.result.commandDispatchResult.commandExecuted
|
||||
? kSuccess
|
||||
: kTextMuted,
|
||||
: kShellPalette.textMuted,
|
||||
11.0f);
|
||||
addStateLine("Result", m_lastStatus, m_lastColor);
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f);
|
||||
addStateLine("结果", m_lastStatus, m_lastColor);
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kShellPalette.textMuted, 11.0f);
|
||||
stateY += 34.0f;
|
||||
addStateLine("Visible Panels", JoinVisiblePanelIds(m_controller.GetWorkspace(), m_controller.GetSession()), kTextWeak, 11.0f);
|
||||
addStateLine("Host Capture", FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted, 11.0f);
|
||||
addStateLine("可见面板", JoinVisiblePanelIds(m_controller.GetWorkspace(), m_controller.GetSession()), kShellPalette.textWeak, 11.0f);
|
||||
addStateLine("宿主捕获", FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted, 11.0f);
|
||||
addStateLine(
|
||||
"Screenshot",
|
||||
"截图",
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
? "截图排队中..."
|
||||
? "截图请求排队中..."
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("F12 或 按钮 -> captures/")
|
||||
? std::string("F12 或按钮 -> captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary()),
|
||||
kTextWeak,
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
|
||||
DrawCard(drawList, m_previewRect, "Preview", "真实 UIEditorShellInteraction 预览,不接旧 editor 业务。");
|
||||
AppendUIEditorShellInteraction(drawList, m_cachedFrame, m_interactionState);
|
||||
DrawCard(drawList, m_previewRect, "预览", "这里是实际 UIEditorShellInteraction 预览,不接 editor 业务层。");
|
||||
const auto palette = ResolveUIEditorShellInteractionPalette();
|
||||
AppendUIEditorShellInteraction(
|
||||
drawList,
|
||||
m_cachedFrame,
|
||||
m_interactionState,
|
||||
palette,
|
||||
metrics);
|
||||
|
||||
const bool framePresented = m_renderer.Render(drawData);
|
||||
m_autoScreenshot.CaptureIfRequested(
|
||||
@@ -1013,9 +1034,9 @@ void ScenarioApp::RenderFrame() {
|
||||
static_cast<unsigned int>(height),
|
||||
framePresented);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
|
||||
return ScenarioApp().Run(hInstance, nCmdShow);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorEnumFieldInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Widgets/UIEditorEnumField.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
|
||||
#include <XCEditor/Fields/UIEditorEnumFieldInteraction.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorEnumField.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuPopup.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -51,8 +51,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorEnumFieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorEnumFieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorEnumFieldSpec;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorMenuPopupInvalidIndex;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorEnumFieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | EnumField Basic";
|
||||
|
||||
@@ -84,11 +82,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
@@ -142,7 +135,7 @@ ScenarioLayout BuildScenarioLayout(
|
||||
layout.previewRect.x + 24.0f,
|
||||
layout.previewRect.y + 82.0f,
|
||||
340.0f,
|
||||
32.0f);
|
||||
22.0f);
|
||||
|
||||
const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f;
|
||||
const float buttonY = layout.controlRect.y + 32.0f;
|
||||
@@ -387,14 +380,6 @@ private:
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/enum_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad =
|
||||
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
return true;
|
||||
@@ -423,7 +408,7 @@ private:
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
UIRect GetViewportRect() const {
|
||||
@@ -458,8 +443,8 @@ private:
|
||||
}
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(m_theme);
|
||||
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme);
|
||||
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics();
|
||||
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics();
|
||||
m_spec.selectedIndex = m_selectedIndex;
|
||||
m_frame = UpdateUIEditorEnumFieldInteraction(
|
||||
m_interactionState,
|
||||
@@ -563,8 +548,8 @@ private:
|
||||
|
||||
UIEditorEnumFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(m_theme);
|
||||
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme);
|
||||
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics();
|
||||
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics();
|
||||
m_spec.selectedIndex = m_selectedIndex;
|
||||
m_frame = UpdateUIEditorEnumFieldInteraction(
|
||||
m_interactionState,
|
||||
@@ -581,7 +566,7 @@ private:
|
||||
|
||||
void UpdateResultText(const UIEditorEnumFieldInteractionResult& result) {
|
||||
if (result.selectionChanged && m_selectedIndex < m_spec.options.size()) {
|
||||
m_lastResult = std::string("已切换到: ") + m_spec.options[m_selectedIndex];
|
||||
m_lastResult = std::string("已切换选项: ") + m_spec.options[m_selectedIndex];
|
||||
return;
|
||||
}
|
||||
if (result.popupOpened) {
|
||||
@@ -627,17 +612,17 @@ private:
|
||||
}
|
||||
|
||||
const UIRect viewportRect = GetViewportRect();
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(viewportRect.width, viewportRect.height, shellMetrics);
|
||||
RefreshFrame();
|
||||
|
||||
const UIEditorEnumFieldHitTarget currentHit =
|
||||
HitTestUIEditorEnumField(m_frame.layout, m_mousePosition);
|
||||
const auto enumMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(m_theme);
|
||||
const auto enumPalette = XCEngine::UI::Editor::ResolveUIEditorEnumFieldPalette(m_theme);
|
||||
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme);
|
||||
const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette(m_theme);
|
||||
const auto enumMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics();
|
||||
const auto enumPalette = XCEngine::UI::Editor::ResolveUIEditorEnumFieldPalette();
|
||||
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics();
|
||||
const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette();
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorEnumFieldBasic");
|
||||
@@ -648,26 +633,26 @@ private:
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试在验证什么功能",
|
||||
"只验证 Editor EnumField 的基础交互契约,不涉及 PropertyGrid 或业务 Inspector。");
|
||||
"这个测试在验证什么功能?",
|
||||
"验证枚举字段的下拉打开/关闭、键盘切换、高亮同步,以及固定 Inspector 风格承载。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 点击 value box 或 dropdown arrow,检查下拉菜单是否展开/收起。",
|
||||
"1. 点击 value box 或 dropdown arrow,应该打开或关闭下拉菜单。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 展开后 hover 列表项,检查高亮是否稳定跟随。",
|
||||
"2. 打开后检查 hover 和高亮项是否同步更新。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. 获得 focus 后按 Up / Down / Home / End,再按 Enter / Space 选中,Esc 关闭。",
|
||||
"3. 获得 focus 后,Up / Down / Home / End 切换高亮;Enter / Space 确认;Esc 关闭。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
|
||||
"4. 检查 Hover / Popup / Highlight / Selected / Result 是否同步更新。",
|
||||
"4. 观察 Hover / Popup / Highlight / Selected / Result 是否同步。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
@@ -735,11 +720,6 @@ private:
|
||||
captureSummary,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
@@ -747,7 +727,7 @@ private:
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"EnumField 预览",
|
||||
"这里只放一个 Unity 风格 EnumField。");
|
||||
"这里只放一个固定样式的枚举字段预览。");
|
||||
AppendUIEditorEnumField(
|
||||
drawList,
|
||||
layout.fieldRect,
|
||||
@@ -783,12 +763,10 @@ private:
|
||||
std::size_t m_selectedIndex = 0u;
|
||||
UIEditorEnumFieldInteractionState m_interactionState = {};
|
||||
UIEditorEnumFieldInteractionFrame m_frame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_hoveredAction = ActionId::Reset;
|
||||
bool m_hasHoveredAction = false;
|
||||
std::string m_lastResult = {};
|
||||
std::string m_themeStatus = "fallback";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -643,31 +643,31 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.introRect,
|
||||
"这个测试在验证什么功能",
|
||||
"只验证 Editor ListView 基础控件,不涉及任何业务面板。");
|
||||
"这个测试验证什么功能?",
|
||||
"只验证 Editor ListView 基础控件:行布局、单选、focus 和键盘导航。不涉及任何业务面板。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 验证列表行的垂直排列是否稳定:主标题和次标题不能互相挤压。",
|
||||
"1. 检查列表行垂直排布是否稳定:主标题和次标题不能互相挤压。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 点击 row 只切换 selection;hover、selected、focused 三种状态要能区分。",
|
||||
"2. 点击 row 只切换 selection;hover、selected、focused 必须能区分。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. 当列表获得 focus 后,按 Up / Down / Home / End 应稳定移动当前选择。",
|
||||
"3. 列表获得 focus 后,按 Up / Down / Home / End 应稳定移动当前选择。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
|
||||
"4. 点击列表外空白后,focus 应清除,但 selection 不应丢失。",
|
||||
"4. 点击列表外空白后,focus 应清除,但 selection 必须保留。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可自动截图。",
|
||||
"5. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可启动自动截图。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
|
||||
@@ -727,7 +727,7 @@ private:
|
||||
kTextWeak,
|
||||
12.0f);
|
||||
|
||||
DrawCard(drawList, layout.previewRect, "ListView 预览", "这里只放一个 ListView,不混入 Project/Console 等业务内容。");
|
||||
DrawCard(drawList, layout.previewRect, "ListView 预览", "这里只放一个 ListView,不混入 Project / Console 等业务内容。");
|
||||
AppendUIEditorListViewBackground(
|
||||
drawList,
|
||||
m_listFrame.layout,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Collections/UIEditorInlineRenameSession.h>
|
||||
#include <XCEditor/Collections/UIEditorListView.h>
|
||||
#include <XCEditor/Collections/UIEditorListViewInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Fields/UIEditorTextField.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
@@ -14,7 +15,6 @@
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEngine/UI/Style/Theme.h>
|
||||
#include <XCEngine/UI/Widgets/UISelectionModel.h>
|
||||
|
||||
#include <windows.h>
|
||||
@@ -33,6 +33,8 @@
|
||||
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
|
||||
#endif
|
||||
|
||||
// 场景说明:验证固定样式下 ListView 的 inline rename 提交、取消与外部点击提交。
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
@@ -47,8 +49,8 @@ using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Widgets::UISelectionModel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics;
|
||||
using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette;
|
||||
using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics;
|
||||
using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette;
|
||||
using XCEngine::UI::Editor::BuildUIEditorInlineRenameTextFieldMetrics;
|
||||
using XCEngine::UI::Editor::Host::AutoScreenshotController;
|
||||
using XCEngine::UI::Editor::Host::InputModifierTracker;
|
||||
@@ -77,8 +79,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorListViewInvalidIndex;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorListViewItem;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorListViewInlineRenameValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ListView Inline Rename";
|
||||
|
||||
@@ -111,11 +111,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
@@ -242,12 +237,12 @@ void DrawButton(
|
||||
|
||||
std::vector<UIEditorListViewItem> BuildListItems() {
|
||||
return {
|
||||
{ "scene", "SampleScene.unity", "Scene | 4 分钟前修改", 0.0f },
|
||||
{ "lighting", "LightingProfile.asset", "Preset | 3 个 profile", 0.0f },
|
||||
{ "scene", "SampleScene.unity", "Scene | modified 4 minutes ago", 0.0f },
|
||||
{ "lighting", "LightingProfile.asset", "Preset | 3 profiles", 0.0f },
|
||||
{ "material", "RobotBody.mat", "Material | Metallic Workflow", 0.0f },
|
||||
{ "script", "PlayerController.cs", "C# Script | 3.4 KB", 0.0f },
|
||||
{ "texture", "Checker_AO.png", "Texture2D | 2048x2048", 0.0f },
|
||||
{ "prefab", "Robot.prefab", "Prefab | 9 个组件", 0.0f },
|
||||
{ "prefab", "Robot.prefab", "Prefab | 9 children", 0.0f },
|
||||
{ "anim", "Walk.anim", "Animation Clip | 1.2 s", 0.0f },
|
||||
{ "shader", "Outline.shader", "Shader | URP compatible", 0.0f }
|
||||
};
|
||||
@@ -346,8 +341,6 @@ private:
|
||||
AutoScreenshotController m_autoScreenshot = {};
|
||||
InputModifierTracker m_modifierTracker = {};
|
||||
std::filesystem::path m_captureRoot = {};
|
||||
Style::UITheme m_theme = {};
|
||||
std::string m_themeStatus = "fallback";
|
||||
std::vector<UIEditorListViewItem> m_items = {};
|
||||
UISelectionModel m_selectionModel = {};
|
||||
UIEditorListViewInteractionState m_listInteractionState = {};
|
||||
@@ -524,13 +517,6 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/list_view_inline_rename/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
|
||||
const auto themeLoad = XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
m_modifierTracker.SyncFromSystemState();
|
||||
ResetScenario();
|
||||
@@ -560,7 +546,7 @@ ScenarioLayout ScenarioApp::GetLayout() const {
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
void ScenarioApp::ResetScenario() {
|
||||
@@ -769,7 +755,7 @@ void ScenarioApp::HandleWindowFocusLost() {
|
||||
PumpRenameEvents({ MakeFocusEvent(UIInputEventType::FocusLost) });
|
||||
} else {
|
||||
PumpListEvents({ MakeFocusEvent(UIInputEventType::FocusLost) });
|
||||
m_lastResult = "窗口失去焦点,ListView focus 已清除。";
|
||||
m_lastResult = "Window focus lost. ListView focus cleared.";
|
||||
}
|
||||
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
@@ -867,7 +853,6 @@ void ScenarioApp::ApplyRenameFrame(const UIEditorInlineRenameSessionFrame& frame
|
||||
m_lastCommittedValue = result.valueAfter;
|
||||
RefreshListFrame();
|
||||
}
|
||||
|
||||
m_lastResult = "已提交 rename: " + result.itemId + " -> " + result.valueAfter;
|
||||
return;
|
||||
}
|
||||
@@ -918,9 +903,9 @@ UIEditorInlineRenameSessionRequest ScenarioApp::BuildRenameRequest(bool beginSes
|
||||
}
|
||||
|
||||
UIEditorTextFieldMetrics ScenarioApp::ResolveHostedTextFieldMetrics() const {
|
||||
const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics(m_theme);
|
||||
const auto textMetrics = ResolveUIEditorTextFieldMetrics(m_theme);
|
||||
return BuildUIEditorHostedTextFieldMetrics(propertyMetrics, textMetrics);
|
||||
const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics();
|
||||
const auto textMetrics = ResolveUIEditorTextFieldMetrics();
|
||||
return BuildUIEditorPropertyGridTextFieldMetrics(propertyMetrics, textMetrics);
|
||||
}
|
||||
|
||||
UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& bounds) const {
|
||||
@@ -928,9 +913,9 @@ UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& b
|
||||
}
|
||||
|
||||
UIEditorTextFieldPalette ScenarioApp::ResolveInlineRenamePalette() const {
|
||||
const auto propertyPalette = ResolveUIEditorPropertyGridPalette(m_theme);
|
||||
const auto textPalette = ResolveUIEditorTextFieldPalette(m_theme);
|
||||
return BuildUIEditorHostedTextFieldPalette(propertyPalette, textPalette);
|
||||
const auto propertyPalette = ResolveUIEditorPropertyGridPalette();
|
||||
const auto textPalette = ResolveUIEditorTextFieldPalette();
|
||||
return BuildUIEditorPropertyGridTextFieldPalette(propertyPalette, textPalette);
|
||||
}
|
||||
|
||||
void ScenarioApp::UpdateListResultText(const UIEditorListViewInteractionResult& result) {
|
||||
@@ -978,8 +963,8 @@ void ScenarioApp::RenderFrame() {
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
RefreshListFrame();
|
||||
|
||||
@@ -1002,36 +987,36 @@ void ScenarioApp::RenderFrame() {
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试在验证什么功能?",
|
||||
"只验证 Editor ListView 的 inline rename 契约:默认直接进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。");
|
||||
"这个测试验证什么功能?",
|
||||
"只验证 Editor ListView 的 inline rename:默认进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 启动后默认直接进入 script 的 rename,检查输入框是否只覆盖当前选中行主文本。",
|
||||
"1. 启动后默认直接进入 script 的 rename,输入框只能覆盖当前选中行主文本。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 输入字符后,检查 Draft 是否实时变化,且列表原文本被 overlay 正确遮住,不应双层叠字。",
|
||||
"2. 输入字符后,Draft 必须实时变化,原文本不能和 overlay 叠字。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. 按 Enter,检查名称是否写回到对应 ListViewItem.primaryText,并关闭 rename。",
|
||||
"3. 按 Enter:名称写回 ListViewItem.primaryText,并退出 rename。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
|
||||
"4. 按 Esc,检查是否取消编辑并保留原文本。",
|
||||
"4. 按 Esc:取消编辑,保留原文本。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. rename 过程中点击输入框外部,检查是否提交当前草稿并退出编辑态。",
|
||||
"5. rename 过程中点击输入框外部:提交当前草稿,并退出编辑态。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f),
|
||||
"6. 需要时可先按 Esc 退出,再用 Up/Down/Home/End 切换选中项后按 F2;F12 或按钮触发截图。",
|
||||
"6. 需要时先按 Esc 退出,再用 Up/Down/Home/End 切换选中项后按 F2;F12 可截图。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
@@ -1116,7 +1101,7 @@ void ScenarioApp::RenderFrame() {
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
"Style: fixed",
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEngine/UI/Style/Theme.h>
|
||||
#include <XCEngine/UI/Widgets/UISelectionModel.h>
|
||||
|
||||
#include <windows.h>
|
||||
@@ -56,8 +55,6 @@ using XCEngine::UI::Editor::Widgets::AppendUIEditorListViewForeground;
|
||||
using XCEngine::UI::Editor::Widgets::HitTestUIEditorListView;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorListViewHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorListViewItem;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorListViewMultiSelectValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ListView MultiSelect";
|
||||
|
||||
@@ -90,11 +87,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
@@ -196,12 +188,12 @@ void DrawButton(
|
||||
|
||||
std::vector<UIEditorListViewItem> BuildListItems() {
|
||||
return {
|
||||
{ "scene", "SampleScene.unity", "Scene | 4 分钟前修改", 0.0f },
|
||||
{ "lighting", "LightingProfile.asset", "Preset | 3 个 profile", 0.0f },
|
||||
{ "scene", "SampleScene.unity", "Scene | modified 4 minutes ago", 0.0f },
|
||||
{ "lighting", "LightingProfile.asset", "Preset | 3 profiles", 0.0f },
|
||||
{ "material", "RobotBody.mat", "Material | Metallic Workflow", 0.0f },
|
||||
{ "script", "PlayerController.cs", "C# Script | 3.4 KB", 0.0f },
|
||||
{ "texture", "Checker_AO.png", "Texture2D | 2048x2048", 0.0f },
|
||||
{ "prefab", "Robot.prefab", "Prefab | 9 个组件", 0.0f },
|
||||
{ "prefab", "Robot.prefab", "Prefab | 9 children", 0.0f },
|
||||
{ "anim", "Walk.anim", "Animation Clip | 1.2 s", 0.0f },
|
||||
{ "shader", "Outline.shader", "Shader | URP compatible", 0.0f },
|
||||
{ "mesh", "Robot.fbx", "Model | 38k triangles", 0.0f },
|
||||
@@ -439,14 +431,6 @@ private:
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/list_view_multiselect/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
|
||||
const auto themeLoad =
|
||||
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
m_modifierTracker.SyncFromSystemState();
|
||||
ResetScenario();
|
||||
@@ -476,7 +460,7 @@ private:
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
@@ -489,7 +473,7 @@ private:
|
||||
m_hasHoveredAction = false;
|
||||
m_hoveredAction = ActionId::Reset;
|
||||
m_lastModifiers = {};
|
||||
m_lastResult = "已重置到默认多选状态";
|
||||
m_lastResult = "Reset to the default multiselect state.";
|
||||
RefreshFrame();
|
||||
}
|
||||
|
||||
@@ -605,7 +589,7 @@ private:
|
||||
|
||||
if (wParam == VK_F12) {
|
||||
m_autoScreenshot.RequestCapture("manual_f12");
|
||||
m_lastResult = "已请求截图,输出到 captures/latest.png";
|
||||
m_lastResult = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>??captures/latest.png";
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
return;
|
||||
}
|
||||
@@ -675,13 +659,13 @@ private:
|
||||
}
|
||||
|
||||
if (result.keyboardNavigated && !result.selectedItemId.empty()) {
|
||||
m_lastResult = "键盘导航到: " + result.selectedItemId;
|
||||
m_lastResult = "<EFBFBD><EFBFBD><EFBFBD>̵<EFBFBD><EFBFBD><EFBFBD>?? " + result.selectedItemId;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.selectionChanged) {
|
||||
m_lastResult =
|
||||
"选集已更新: primary=" + m_selectionModel.GetSelectedId() +
|
||||
"ѡ<EFBFBD><EFBFBD><EFBFBD>Ѹ<EFBFBD>?? primary=" + m_selectionModel.GetSelectedId() +
|
||||
" | ids=" + JoinSelectedIds(m_selectionModel);
|
||||
return;
|
||||
}
|
||||
@@ -692,7 +676,7 @@ private:
|
||||
}
|
||||
|
||||
if (insideList) {
|
||||
m_lastResult = "点击列表内空白,只更新 hover / focus";
|
||||
m_lastResult = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD>ڿհף<EFBFBD>ֻ<EFBFBD><EFBFBD>??hover / focus";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -706,7 +690,7 @@ private:
|
||||
break;
|
||||
case ActionId::Capture:
|
||||
m_autoScreenshot.RequestCapture("manual_button");
|
||||
m_lastResult = "已请求截图,输出到 captures/latest.png";
|
||||
m_lastResult = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>??captures/latest.png";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -720,8 +704,8 @@ private:
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
RefreshFrame();
|
||||
|
||||
@@ -737,7 +721,7 @@ private:
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试在验证什么功能?",
|
||||
"这个测试验证什么功能?",
|
||||
"只验证 Editor ListView 多选 contract:Ctrl/Shift 选集、右键 primary 切换、键盘范围扩展,不涉及任何业务面板。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
@@ -845,11 +829,6 @@ private:
|
||||
captureSummary,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
@@ -881,8 +860,6 @@ private:
|
||||
AutoScreenshotController m_autoScreenshot = {};
|
||||
InputModifierTracker m_modifierTracker = {};
|
||||
std::filesystem::path m_captureRoot = {};
|
||||
Style::UITheme m_theme = {};
|
||||
std::string m_themeStatus = "fallback";
|
||||
std::vector<UIEditorListViewItem> m_items = {};
|
||||
UISelectionModel m_selectionModel = {};
|
||||
UIEditorListViewInteractionState m_interactionState = {};
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorCommandDispatcher.h>
|
||||
#include <XCEditor/Core/UIEditorMenuModel.h>
|
||||
#include <XCEditor/Core/UIEditorMenuSession.h>
|
||||
#include <XCEditor/Core/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuBar.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
|
||||
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuModel.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuSession.h>
|
||||
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuBar.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuPopup.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -866,9 +866,9 @@ void ScenarioApp::ResetScenario() {
|
||||
m_menuPopups.clear();
|
||||
m_menuItems.clear();
|
||||
SetCustomResult(
|
||||
"等待操作",
|
||||
"绛夊緟鎿嶄綔",
|
||||
"Pending",
|
||||
"先点 File / Window / Layout,确认一次只会打开一个菜单;再点菜单外区域或按 Escape 关闭。");
|
||||
"鍏堢偣 File / Window / Layout锛岀‘璁や竴娆″彧浼氭墦寮€涓€涓彍鍗曪紱鍐嶇偣鑿滃崟澶栧尯鍩熸垨鎸?Escape 鍏抽棴銆?);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1004,7 +1004,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
|
||||
SetCustomResult(
|
||||
"Hover 切换顶层菜单",
|
||||
"Changed",
|
||||
"已切换到顶层菜单 `" + hoveredButton->label + "`。重点检查:旧 root popup 被替换,新 root popup 立即出现。");
|
||||
"已切换到顶层菜单 `" + hoveredButton->label + "`。重点检查:旧 root popup 被替换后,新 root popup 是否立即出现。");
|
||||
dirty = true;
|
||||
}
|
||||
} else {
|
||||
@@ -1014,7 +1014,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
|
||||
SetCustomResult(
|
||||
"Hover 收起子菜单",
|
||||
"Dismissed",
|
||||
"鼠标回到顶层按钮后,旧的 child popup 已收起。closed: " +
|
||||
"鼠标回到顶层按钮后,旧的 child popup 已收起。Closed: " +
|
||||
JoinClosedPopupIds(mutation));
|
||||
dirty = true;
|
||||
}
|
||||
@@ -1040,7 +1040,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
|
||||
SetCustomResult(
|
||||
"Hover 收起子菜单",
|
||||
"Dismissed",
|
||||
"鼠标移到普通菜单项后,旧的 child popup 已收起。closed: " +
|
||||
"鼠标移到普通菜单项后,旧的 child popup 已收起。Closed: " +
|
||||
JoinClosedPopupIds(mutation));
|
||||
dirty = true;
|
||||
}
|
||||
@@ -1052,7 +1052,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
|
||||
SetCustomResult(
|
||||
"Hover popup 空白区",
|
||||
"Dismissed",
|
||||
"鼠标停留在 popup 空白区后,子菜单链已回收到当前 popup。closed: " +
|
||||
"鼠标停留在 popup 空白区后,子菜单链已回收到当前 popup。Closed: " +
|
||||
JoinClosedPopupIds(mutation));
|
||||
dirty = true;
|
||||
}
|
||||
@@ -1075,7 +1075,7 @@ void ScenarioApp::HandleClick(float x, float y) {
|
||||
"点击关闭顶层菜单",
|
||||
mutation.changed ? "Dismissed" : "NoOp",
|
||||
mutation.changed
|
||||
? "再次点击当前顶层按钮后,整条菜单链已关闭。closed: " +
|
||||
? "再次点击当前顶层按钮后,整条菜单链已关闭。Closed: " +
|
||||
JoinClosedPopupIds(mutation)
|
||||
: "当前顶层菜单没有发生变化。");
|
||||
} else {
|
||||
@@ -1108,8 +1108,8 @@ void ScenarioApp::HandleClick(float x, float y) {
|
||||
mutation.changed ? "Changed" : "NoOp",
|
||||
mutation.changed
|
||||
? "已展开 `" + hoveredItem->label +
|
||||
"` 子菜单。这个场景的正常行为是 hover 也会直接展开。"
|
||||
: "子菜单已经处于打开状态。");
|
||||
"` 子菜单。这个场景里正常行为是 hover 也会直接展开。"
|
||||
: "子菜单已经处于展开状态。");
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
return;
|
||||
}
|
||||
@@ -1140,7 +1140,7 @@ void ScenarioApp::HandleClick(float x, float y) {
|
||||
SetCustomResult(
|
||||
"点击 popup 空白区",
|
||||
"Dismissed",
|
||||
"点击当前 popup 空白区后,仅更深层子菜单被关闭。closed: " +
|
||||
"点击当前 popup 空白区后,仅更深层子菜单被关闭。Closed: " +
|
||||
JoinClosedPopupIds(mutation));
|
||||
ClearHoverWhenMenuClosed();
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
@@ -1155,7 +1155,7 @@ void ScenarioApp::HandleClick(float x, float y) {
|
||||
"点击菜单外区域",
|
||||
mutation.changed ? "Dismissed" : "NoOp",
|
||||
mutation.changed
|
||||
? "点击外部区域后,整条菜单链已关闭。closed: " +
|
||||
? "点击外部区域后,整条菜单链已关闭。Closed: " +
|
||||
JoinClosedPopupIds(mutation)
|
||||
: "菜单链没有变化。");
|
||||
ClearHoverWhenMenuClosed();
|
||||
@@ -1172,14 +1172,14 @@ void ScenarioApp::HandleClick(float x, float y) {
|
||||
if (m_openMenuId == button.menuId) {
|
||||
m_openMenuId.clear();
|
||||
m_hoveredItemId.clear();
|
||||
SetCustomResult("关闭菜单", "NoOp", "再次点击当前菜单按钮,菜单已关闭。");
|
||||
SetCustomResult("鍏抽棴鑿滃崟", "NoOp", "鍐嶆鐐瑰嚮褰撳墠鑿滃崟鎸夐挳锛岃彍鍗曞凡鍏抽棴銆?);
|
||||
} else {
|
||||
m_openMenuId = button.menuId;
|
||||
m_hoveredItemId.clear();
|
||||
SetCustomResult(
|
||||
"打开菜单",
|
||||
"鎵撳紑鑿滃崟",
|
||||
"Changed",
|
||||
"当前激活菜单: " + button.label + "。确认同一时刻只存在一个下拉菜单。");
|
||||
"褰撳墠婵€娲昏彍鍗? " + button.label + "銆傜‘璁ゅ悓涓€鏃跺埢鍙瓨鍦ㄤ竴涓笅鎷夎彍鍗曘€?);
|
||||
}
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
return;
|
||||
@@ -1197,9 +1197,9 @@ void ScenarioApp::HandleClick(float x, float y) {
|
||||
|
||||
if (!item.enabled) {
|
||||
SetCustomResult(
|
||||
"菜单项不可执行",
|
||||
"鑿滃崟椤逛笉鍙墽琛?,
|
||||
"Disabled",
|
||||
"当前工作区状态下 `" + item.label + "` 不可执行。");
|
||||
"褰撳墠宸ヤ綔鍖虹姸鎬佷笅 `" + item.label + "` 涓嶅彲鎵ц銆?);
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
return;
|
||||
}
|
||||
@@ -1216,7 +1216,7 @@ void ScenarioApp::HandleClick(float x, float y) {
|
||||
if (!m_openMenuId.empty()) {
|
||||
m_openMenuId.clear();
|
||||
m_hoveredItemId.clear();
|
||||
SetCustomResult("菜单失焦", "Dismissed", "点击菜单外区域,菜单已关闭。");
|
||||
SetCustomResult("鑿滃崟澶辩劍", "Dismissed", "鐐瑰嚮鑿滃崟澶栧尯鍩燂紝鑿滃崟宸插叧闂€?);
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
}
|
||||
#endif
|
||||
@@ -1255,13 +1255,13 @@ void ScenarioApp::HandleKeyDown(UINT keyCode) {
|
||||
if (!m_openMenuId.empty()) {
|
||||
m_openMenuId.clear();
|
||||
m_hoveredItemId.clear();
|
||||
SetCustomResult("Esc 关闭菜单", "Dismissed", "按下 Escape 后,菜单已关闭。");
|
||||
SetCustomResult("Esc 鍏抽棴鑿滃崟", "Dismissed", "鎸変笅 Escape 鍚庯紝鑿滃崟宸插叧闂€?);
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
SetDispatchResult(
|
||||
"键盘 Reset Workspace",
|
||||
"閿洏 Reset Workspace",
|
||||
m_commandDispatcher.Dispatch("workspace.reset", m_controller));
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
break;
|
||||
@@ -1446,13 +1446,13 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
|
||||
DrawCard(
|
||||
drawList,
|
||||
headerRect,
|
||||
"测试内容:Editor Menu 会话状态层",
|
||||
"这个测试验证什么功能?",
|
||||
"本场景只验证顶层菜单切换、child popup hover、Esc / outside dismiss、命令派发;不验证业务面板。");
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点击 File 打开 root popup,再 hover `Workspace Tools`,右侧 child popup 应立即弹出。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. root popup 打开时,把鼠标移到 Window / Layout 顶层按钮,旧 popup 应直接被新的 root popup 替换。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 点击 Window -> Activate Details,再点 Window -> Hide Active,检查右侧 details visible / active 是否按状态变化。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. 如果 child popup 已打开,按一次 Esc 只关 topmost;再按一次 Esc 关闭整条菜单链。点击外部空白区也必须整条关闭。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. F12 保存截图;R 可直接触发 Reset Workspace,方便确认命令派发仍正常。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. F12 保存截图,R 可直接触发 Reset Workspace,方便确认命令派发仍正常。", kTextPrimary, 13.0f);
|
||||
|
||||
DrawCard(drawList, shellRect, "操作区", "这里只保留 MenuBar 和当前 popup overlay。");
|
||||
DrawCard(drawList, stateRect, "状态摘要", "重点看 menu session 当前链路和 Details 状态,不放无关杂项。");
|
||||
@@ -1464,15 +1464,15 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
|
||||
const UIRect shellInfoRect(shellRect.x + 18.0f, shellRect.y + 144.0f, shellRect.width - 36.0f, 190.0f);
|
||||
drawList.AddFilledRect(shellInfoRect, kIndicatorBg, 8.0f);
|
||||
drawList.AddRectOutline(shellInfoRect, kCardBorder, 1.0f, 8.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 14.0f), "Open root menu", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 14.0f), "已打开 root menu", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 34.0f), m_menuSession.HasOpenMenu() ? std::string(m_menuSession.GetOpenRootMenuId()) : "(none)", kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 62.0f), "Popup chain", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 62.0f), "Popup 链", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 82.0f), JoinPopupChainIds(m_menuSession), kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 110.0f), "Submenu path", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 110.0f), "Submenu 路径", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 130.0f), JoinSubmenuPathIds(m_menuSession), kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 158.0f), "提示:popup overlay 必须压在右侧状态面板上方,这一轮会专门检查这个层级关系。", kTextMuted, 12.0f);
|
||||
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 72.0f), "Hover summary", kAccent, 15.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 72.0f), "Hover 摘要", kAccent, 15.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 100.0f), "hover menu", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 120.0f), m_hoveredMenuId.empty() ? "(none)" : m_hoveredMenuId, kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 150.0f), "hover popup", kTextMuted, 12.0f);
|
||||
@@ -1480,19 +1480,19 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 200.0f), "hover item", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 220.0f), m_hoveredItemId.empty() ? "(none)" : m_hoveredItemId, kTextPrimary, 14.0f);
|
||||
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 262.0f), "Workspace summary", kAccent, 15.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 290.0f), "active panel", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 262.0f), "Workspace 摘要", kAccent, 15.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 290.0f), "当前激活面板", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 310.0f), workspace.activePanelId.empty() ? "(none)" : workspace.activePanelId, kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 338.0f), "visible panels", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 338.0f), "可见面板", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 358.0f), JoinVisiblePanelIds(workspace, session), kTextPrimary, 14.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 386.0f), "details visible", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 386.0f), "Details 可见", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 406.0f), detailsState != nullptr && detailsState->visible ? "true" : "false", detailsState != nullptr && detailsState->visible ? kSuccess : kWarning, 14.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 434.0f), "details active", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 434.0f), "Details 激活", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 454.0f), workspace.activePanelId == "details" ? "true" : "false", workspace.activePanelId == "details" ? kSuccess : kTextMuted, 14.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 482.0f), "menu model validation", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 482.0f), "菜单模型验证", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 502.0f), menuValidation.IsValid() ? "OK" : menuValidation.message, menuValidation.IsValid() ? kSuccess : kDanger, 12.0f);
|
||||
|
||||
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 56.0f), "Last interaction: " + m_lastActionName + " | Result: " + m_lastStatusLabel, m_lastStatusColor, 13.0f);
|
||||
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 56.0f), "最近交互: " + m_lastActionName + " | Result: " + m_lastStatusLabel, m_lastStatusColor, 13.0f);
|
||||
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 74.0f), m_lastMessage, kTextPrimary, 12.0f);
|
||||
|
||||
const std::string captureSummary =
|
||||
@@ -1630,7 +1630,7 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
|
||||
DrawCard(
|
||||
drawList,
|
||||
headerRect,
|
||||
"测试内容:Editor Menu 基础壳层验证",
|
||||
"这个测试验证什么功能?",
|
||||
"只验证 MenuBar / 下拉展开 / hover / 菜单关闭 / command dispatch;不验证业务面板,不验证完整编辑器菜单体系。");
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点击 File / Window / Layout,确认同一时刻只会有一个菜单展开。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. 展开菜单后移动鼠标,确认 hover 高亮稳定,disabled 项不会误显示成可点击。", kTextPrimary, 13.0f);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorNumberFieldInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Widgets/UIEditorNumberField.h>
|
||||
#include <XCEditor/Fields/UIEditorNumberFieldInteraction.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Fields/UIEditorNumberField.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -48,7 +49,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorNumberField;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorNumberFieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorNumberFieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorNumberFieldSpec;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorNumberFieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | NumberField Basic";
|
||||
@@ -84,10 +84,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
@@ -170,18 +166,16 @@ ScenarioLayout BuildScenarioLayout(
|
||||
return layout;
|
||||
}
|
||||
|
||||
XCEngine::UI::Editor::Widgets::UIEditorNumberFieldMetrics ResolveHostedNumberFieldMetrics(
|
||||
const Style::UITheme& theme) {
|
||||
const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(theme);
|
||||
const auto numberMetrics = XCEngine::UI::Editor::ResolveUIEditorNumberFieldMetrics(theme);
|
||||
return XCEngine::UI::Editor::BuildUIEditorHostedNumberFieldMetrics(propertyMetrics, numberMetrics);
|
||||
XCEngine::UI::Editor::Widgets::UIEditorNumberFieldMetrics ResolvePropertyGridNumberFieldMetrics() {
|
||||
return XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldMetrics(
|
||||
XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(),
|
||||
XCEngine::UI::Editor::ResolveUIEditorNumberFieldMetrics());
|
||||
}
|
||||
|
||||
XCEngine::UI::Editor::Widgets::UIEditorNumberFieldPalette ResolveHostedNumberFieldPalette(
|
||||
const Style::UITheme& theme) {
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(theme);
|
||||
const auto numberPalette = XCEngine::UI::Editor::ResolveUIEditorNumberFieldPalette(theme);
|
||||
return XCEngine::UI::Editor::BuildUIEditorHostedNumberFieldPalette(propertyPalette, numberPalette);
|
||||
XCEngine::UI::Editor::Widgets::UIEditorNumberFieldPalette ResolvePropertyGridNumberFieldPalette() {
|
||||
return XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldPalette(
|
||||
XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(),
|
||||
XCEngine::UI::Editor::ResolveUIEditorNumberFieldPalette());
|
||||
}
|
||||
|
||||
void DrawCard(
|
||||
@@ -430,14 +424,6 @@ private:
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/number_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad =
|
||||
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
return true;
|
||||
@@ -466,7 +452,7 @@ private:
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
@@ -494,7 +480,7 @@ private:
|
||||
}
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = ResolveHostedNumberFieldMetrics(m_theme);
|
||||
const auto metrics = ResolvePropertyGridNumberFieldMetrics();
|
||||
m_frame = UpdateUIEditorNumberFieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -604,7 +590,7 @@ private:
|
||||
|
||||
UIEditorNumberFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = ResolveHostedNumberFieldMetrics(m_theme);
|
||||
const auto metrics = ResolvePropertyGridNumberFieldMetrics();
|
||||
m_frame = UpdateUIEditorNumberFieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -616,7 +602,7 @@ private:
|
||||
|
||||
void UpdateResultText(const UIEditorNumberFieldInteractionResult& result) {
|
||||
if (result.editCommitRejected) {
|
||||
m_lastResult = "提交失败,仍保持在编辑态";
|
||||
m_lastResult = "提交失败,仍保持在编辑状态。";
|
||||
return;
|
||||
}
|
||||
if (result.editCommitted) {
|
||||
@@ -624,11 +610,11 @@ private:
|
||||
return;
|
||||
}
|
||||
if (result.editCanceled) {
|
||||
m_lastResult = "已取消编辑";
|
||||
m_lastResult = "已取消编辑。";
|
||||
return;
|
||||
}
|
||||
if (result.editStarted) {
|
||||
m_lastResult = "已进入编辑态";
|
||||
m_lastResult = "已进入编辑状态。";
|
||||
return;
|
||||
}
|
||||
if (result.valueChanged || result.stepApplied) {
|
||||
@@ -636,7 +622,7 @@ private:
|
||||
return;
|
||||
}
|
||||
if (result.consumed) {
|
||||
m_lastResult = "控件已消费输入";
|
||||
m_lastResult = "控件已消费输入。";
|
||||
return;
|
||||
}
|
||||
m_lastResult = "等待交互";
|
||||
@@ -664,16 +650,16 @@ private:
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
RefreshFrame();
|
||||
|
||||
const UIEditorNumberFieldHitTarget currentHit =
|
||||
HitTestUIEditorNumberField(m_frame.layout, m_mousePosition);
|
||||
const auto numberMetrics = ResolveHostedNumberFieldMetrics(m_theme);
|
||||
const auto numberPalette = ResolveHostedNumberFieldPalette(m_theme);
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
|
||||
const auto numberMetrics = ResolvePropertyGridNumberFieldMetrics();
|
||||
const auto numberPalette = ResolvePropertyGridNumberFieldPalette();
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorNumberFieldBasic");
|
||||
@@ -684,7 +670,7 @@ private:
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试在验证什么功能",
|
||||
"这个测试在验证什么功能?",
|
||||
"验证 Inspector 宿主中的 NumberField 交互契约和默认宿主风格。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
@@ -698,7 +684,7 @@ private:
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. 按 Enter 进入编辑态,直接输入字符,Enter commit,Esc cancel。",
|
||||
"3. 按 Enter 进入编辑态,直接输入字符;Enter commit,Esc cancel。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
@@ -773,7 +759,7 @@ private:
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
"Style: fixed",
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
@@ -783,7 +769,7 @@ private:
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"NumberField 预览",
|
||||
"这里仅预览 Inspector 宿主中的 Unity 风格 Number 字段。");
|
||||
"这里只预览 Inspector 宿主中的 Unity 风格 Number 字段。");
|
||||
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
|
||||
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
|
||||
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
|
||||
@@ -825,12 +811,10 @@ private:
|
||||
UIEditorNumberFieldSpec m_spec = {};
|
||||
UIEditorNumberFieldInteractionState m_interactionState = {};
|
||||
UIEditorNumberFieldInteractionFrame m_frame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_hoveredAction = ActionId::Reset;
|
||||
bool m_hasHoveredAction = false;
|
||||
std::string m_lastResult = {};
|
||||
std::string m_themeStatus = "fallback";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
add_executable(editor_ui_object_field_basic_validation WIN32
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(editor_ui_object_field_basic_validation PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Editor/integration/shared/src
|
||||
${CMAKE_SOURCE_DIR}/new_editor/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/app
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
target_compile_definitions(editor_ui_object_field_basic_validation PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
XCENGINE_EDITOR_UI_TESTS_REPO_ROOT="${XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH}"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(editor_ui_object_field_basic_validation PRIVATE /utf-8 /FS)
|
||||
set_property(TARGET editor_ui_object_field_basic_validation PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
|
||||
target_link_libraries(editor_ui_object_field_basic_validation PRIVATE
|
||||
XCUIEditorLib
|
||||
XCUIEditorHost
|
||||
)
|
||||
|
||||
set_target_properties(editor_ui_object_field_basic_validation PROPERTIES
|
||||
OUTPUT_NAME "XCUIEditorObjectFieldBasicValidation"
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
736
tests/UI/Editor/integration/shell/object_field_basic/main.cpp
Normal file
736
tests/UI/Editor/integration/shell/object_field_basic/main.cpp
Normal file
@@ -0,0 +1,736 @@
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorObjectField.h>
|
||||
#include <XCEditor/Fields/UIEditorObjectFieldInteraction.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT
|
||||
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
using XCEngine::UI::UIDrawData;
|
||||
using XCEngine::UI::UIDrawList;
|
||||
using XCEngine::UI::UIInputEvent;
|
||||
using XCEngine::UI::UIInputEventType;
|
||||
using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::Host::AutoScreenshotController;
|
||||
using XCEngine::UI::Editor::Host::NativeRenderer;
|
||||
using XCEngine::UI::Editor::UIEditorObjectFieldInteractionFrame;
|
||||
using XCEngine::UI::Editor::UIEditorObjectFieldInteractionResult;
|
||||
using XCEngine::UI::Editor::UIEditorObjectFieldInteractionState;
|
||||
using XCEngine::UI::Editor::UpdateUIEditorObjectFieldInteraction;
|
||||
using XCEngine::UI::Editor::Widgets::AppendUIEditorObjectField;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldSpec;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorObjectFieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ObjectField Basic";
|
||||
|
||||
enum class ActionId : unsigned char {
|
||||
Reset = 0,
|
||||
Capture
|
||||
};
|
||||
|
||||
struct ButtonLayout {
|
||||
ActionId action = ActionId::Reset;
|
||||
const char* label = "";
|
||||
UIRect rect = {};
|
||||
};
|
||||
|
||||
struct ScenarioLayout {
|
||||
UIRect introRect = {};
|
||||
UIRect controlRect = {};
|
||||
UIRect stateRect = {};
|
||||
UIRect previewRect = {};
|
||||
UIRect fieldRect = {};
|
||||
std::vector<ButtonLayout> buttons = {};
|
||||
};
|
||||
|
||||
std::filesystem::path ResolveRepoRootPath() {
|
||||
std::string root = XCENGINE_EDITOR_UI_TESTS_REPO_ROOT;
|
||||
if (root.size() >= 2u && root.front() == '"' && root.back() == '"') {
|
||||
root = root.substr(1u, root.size() - 2u);
|
||||
}
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
y >= rect.y &&
|
||||
y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
std::int32_t MapObjectFieldKey(UINT keyCode) {
|
||||
switch (keyCode) {
|
||||
case VK_SPACE:
|
||||
return static_cast<std::int32_t>(KeyCode::Space);
|
||||
case VK_RETURN:
|
||||
return static_cast<std::int32_t>(KeyCode::Enter);
|
||||
case VK_DELETE:
|
||||
return static_cast<std::int32_t>(KeyCode::Delete);
|
||||
case VK_BACK:
|
||||
return static_cast<std::int32_t>(KeyCode::Backspace);
|
||||
default:
|
||||
return static_cast<std::int32_t>(KeyCode::None);
|
||||
}
|
||||
}
|
||||
|
||||
ScenarioLayout BuildScenarioLayout(
|
||||
float width,
|
||||
float height,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics) {
|
||||
const float margin = shellMetrics.margin;
|
||||
constexpr float leftWidth = 440.0f;
|
||||
const float gap = shellMetrics.gap;
|
||||
|
||||
ScenarioLayout layout = {};
|
||||
layout.introRect = UIRect(margin, margin, leftWidth, 236.0f);
|
||||
layout.controlRect = UIRect(margin, layout.introRect.y + layout.introRect.height + gap, leftWidth, 84.0f);
|
||||
layout.stateRect = UIRect(
|
||||
margin,
|
||||
layout.controlRect.y + layout.controlRect.height + gap,
|
||||
leftWidth,
|
||||
(std::max)(232.0f, height - (layout.controlRect.y + layout.controlRect.height + gap) - margin));
|
||||
layout.previewRect = UIRect(
|
||||
leftWidth + margin * 2.0f,
|
||||
margin,
|
||||
(std::max)(420.0f, width - leftWidth - margin * 3.0f),
|
||||
height - margin * 2.0f);
|
||||
layout.fieldRect = UIRect(
|
||||
layout.previewRect.x + 24.0f,
|
||||
layout.previewRect.y + 82.0f,
|
||||
(std::min)(440.0f, layout.previewRect.width - 48.0f),
|
||||
22.0f);
|
||||
|
||||
const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f;
|
||||
const float buttonY = layout.controlRect.y + 32.0f;
|
||||
layout.buttons = {
|
||||
{ ActionId::Reset, "重置", UIRect(layout.controlRect.x + 14.0f, buttonY, buttonWidth, 36.0f) },
|
||||
{ ActionId::Capture, "截图(F12)", UIRect(layout.controlRect.x + 26.0f + buttonWidth, buttonY, buttonWidth, 36.0f) }
|
||||
};
|
||||
return layout;
|
||||
}
|
||||
|
||||
void DrawCard(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics,
|
||||
std::string_view title,
|
||||
std::string_view subtitle = {}) {
|
||||
drawList.AddFilledRect(rect, shellPalette.cardBackground, shellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(rect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius);
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + 16.0f, rect.y + 14.0f),
|
||||
std::string(title),
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.titleFontSize);
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + 16.0f, rect.y + 40.0f),
|
||||
std::string(subtitle),
|
||||
shellPalette.textMuted,
|
||||
shellMetrics.bodyFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawButton(
|
||||
UIDrawList& drawList,
|
||||
const ButtonLayout& button,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics,
|
||||
bool hovered) {
|
||||
drawList.AddFilledRect(
|
||||
button.rect,
|
||||
hovered ? shellPalette.buttonHoverBackground : shellPalette.buttonBackground,
|
||||
shellMetrics.buttonRadius);
|
||||
drawList.AddRectOutline(button.rect, shellPalette.cardBorder, 1.0f, shellMetrics.buttonRadius);
|
||||
drawList.AddText(
|
||||
UIPoint(button.rect.x + 16.0f, button.rect.y + 10.0f),
|
||||
button.label,
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
}
|
||||
|
||||
std::string DescribeHitTarget(const UIEditorObjectFieldHitTarget& hitTarget) {
|
||||
switch (hitTarget.kind) {
|
||||
case UIEditorObjectFieldHitTargetKind::ValueBox:
|
||||
return "value_box";
|
||||
case UIEditorObjectFieldHitTargetKind::ClearButton:
|
||||
return "clear_button";
|
||||
case UIEditorObjectFieldHitTargetKind::PickerButton:
|
||||
return "picker_button";
|
||||
case UIEditorObjectFieldHitTargetKind::Row:
|
||||
return "row";
|
||||
case UIEditorObjectFieldHitTargetKind::None:
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
UIInputEvent MakePointerEvent(
|
||||
UIInputEventType type,
|
||||
const UIPoint& position,
|
||||
UIPointerButton button = UIPointerButton::None) {
|
||||
UIInputEvent event = {};
|
||||
event.type = type;
|
||||
event.position = position;
|
||||
event.pointerButton = button;
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakeFocusEvent(UIInputEventType type) {
|
||||
UIInputEvent event = {};
|
||||
event.type = type;
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakeKeyEvent(std::int32_t keyCode) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::KeyDown;
|
||||
event.keyCode = keyCode;
|
||||
return event;
|
||||
}
|
||||
|
||||
class ScenarioApp {
|
||||
public:
|
||||
int Run(HINSTANCE hInstance, int nCmdShow) {
|
||||
if (!Initialize(hInstance, nCmdShow)) {
|
||||
Shutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
MSG message = {};
|
||||
while (message.message != WM_QUIT) {
|
||||
if (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) {
|
||||
TranslateMessage(&message);
|
||||
DispatchMessageW(&message);
|
||||
continue;
|
||||
}
|
||||
|
||||
RenderFrame();
|
||||
Sleep(8);
|
||||
}
|
||||
|
||||
Shutdown();
|
||||
return static_cast<int>(message.wParam);
|
||||
}
|
||||
|
||||
private:
|
||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
if (message == WM_NCCREATE) {
|
||||
const auto* createStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
|
||||
auto* app = static_cast<ScenarioApp*>(createStruct->lpCreateParams);
|
||||
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(app));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
auto* app = reinterpret_cast<ScenarioApp*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||
if (app == nullptr) {
|
||||
return DefWindowProcW(hwnd, message, wParam, lParam);
|
||||
}
|
||||
return app->HandleMessage(hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
bool Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
m_hInstance = hInstance;
|
||||
|
||||
WNDCLASSEXW windowClass = {};
|
||||
windowClass.cbSize = sizeof(windowClass);
|
||||
windowClass.style = CS_HREDRAW | CS_VREDRAW;
|
||||
windowClass.lpfnWndProc = &ScenarioApp::WndProc;
|
||||
windowClass.hInstance = hInstance;
|
||||
windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
||||
windowClass.lpszClassName = kWindowClassName;
|
||||
|
||||
m_windowClassAtom = RegisterClassExW(&windowClass);
|
||||
if (m_windowClassAtom == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hwnd = CreateWindowExW(
|
||||
0,
|
||||
kWindowClassName,
|
||||
kWindowTitle,
|
||||
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
1360,
|
||||
820,
|
||||
nullptr,
|
||||
nullptr,
|
||||
hInstance,
|
||||
this);
|
||||
if (m_hwnd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ShowWindow(m_hwnd, nCmdShow);
|
||||
UpdateWindow(m_hwnd);
|
||||
|
||||
if (!m_renderer.Initialize(m_hwnd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
m_shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
m_fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorObjectFieldMetrics();
|
||||
m_fieldPalette = XCEngine::UI::Editor::ResolveUIEditorObjectFieldPalette();
|
||||
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/object_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
ResetScenario();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
m_autoScreenshot.Shutdown();
|
||||
m_renderer.Shutdown();
|
||||
|
||||
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
|
||||
DestroyWindow(m_hwnd);
|
||||
}
|
||||
m_hwnd = nullptr;
|
||||
|
||||
if (m_windowClassAtom != 0 && m_hInstance != nullptr) {
|
||||
UnregisterClassW(kWindowClassName, m_hInstance);
|
||||
m_windowClassAtom = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
m_spec = {};
|
||||
m_spec.fieldId = "target";
|
||||
m_spec.label = "Target";
|
||||
m_spec.objectName = "Main Camera";
|
||||
m_spec.objectTypeName = "Camera";
|
||||
m_spec.emptyText = "None (GameObject)";
|
||||
m_spec.hasValue = true;
|
||||
m_spec.showClearButton = true;
|
||||
m_spec.showPickerButton = true;
|
||||
|
||||
m_interactionState = {};
|
||||
m_activateCount = 0u;
|
||||
m_lastResult = "已重置到默认 ObjectField 状态";
|
||||
m_hoveredAction = ActionId::Reset;
|
||||
m_pressedAction = ActionId::Reset;
|
||||
m_hasHoveredAction = false;
|
||||
m_hasPressedAction = false;
|
||||
}
|
||||
|
||||
ScenarioLayout GetLayout() const {
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
|
||||
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
|
||||
return BuildScenarioLayout(width, height, m_shellMetrics);
|
||||
}
|
||||
|
||||
void UpdateHoveredAction(const ScenarioLayout& layout, float x, float y) {
|
||||
for (const ButtonLayout& button : layout.buttons) {
|
||||
if (ContainsPoint(button.rect, x, y)) {
|
||||
m_hoveredAction = button.action;
|
||||
m_hasHoveredAction = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_hasHoveredAction = false;
|
||||
}
|
||||
|
||||
const ButtonLayout* HitTestAction(const ScenarioLayout& layout, float x, float y) const {
|
||||
for (const ButtonLayout& button : layout.buttons) {
|
||||
if (ContainsPoint(button.rect, x, y)) {
|
||||
return &button;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ApplyInteractionResult(const UIEditorObjectFieldInteractionResult& result) {
|
||||
if (result.activateRequested) {
|
||||
++m_activateCount;
|
||||
m_lastResult = "已触发 activateRequested";
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.clearRequested) {
|
||||
m_spec.hasValue = false;
|
||||
m_spec.objectName.clear();
|
||||
m_spec.objectTypeName.clear();
|
||||
m_lastResult = "已触发 clearRequested,当前引用已清空";
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.focusChanged) {
|
||||
m_lastResult = std::string("焦点已切换: ") + (m_interactionState.fieldState.focused ? "focused" : "blurred");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.consumed) {
|
||||
m_lastResult = "输入已消费";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void QueueInput(UIInputEvent event) {
|
||||
m_pendingInputEvents.push_back(std::move(event));
|
||||
}
|
||||
|
||||
void ExecuteAction(ActionId action) {
|
||||
switch (action) {
|
||||
case ActionId::Reset:
|
||||
ResetScenario();
|
||||
break;
|
||||
case ActionId::Capture:
|
||||
m_autoScreenshot.RequestCapture("manual_button");
|
||||
m_lastResult = "截图已排队,输出到 captures/latest.png";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleMouseMove(float x, float y) {
|
||||
m_mousePosition = UIPoint(x, y);
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
UpdateHoveredAction(layout, x, y);
|
||||
|
||||
TRACKMOUSEEVENT trackEvent = {};
|
||||
trackEvent.cbSize = sizeof(trackEvent);
|
||||
trackEvent.dwFlags = TME_LEAVE;
|
||||
trackEvent.hwndTrack = m_hwnd;
|
||||
TrackMouseEvent(&trackEvent);
|
||||
|
||||
QueueInput(MakePointerEvent(UIInputEventType::PointerMove, m_mousePosition));
|
||||
}
|
||||
|
||||
void HandleMouseLeave() {
|
||||
m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
m_hasHoveredAction = false;
|
||||
QueueInput(MakePointerEvent(UIInputEventType::PointerLeave, m_mousePosition));
|
||||
}
|
||||
|
||||
void HandleLeftButtonDown(float x, float y) {
|
||||
SetFocus(m_hwnd);
|
||||
m_mousePosition = UIPoint(x, y);
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
UpdateHoveredAction(layout, x, y);
|
||||
if (const ButtonLayout* button = HitTestAction(layout, x, y)) {
|
||||
m_pressedAction = button->action;
|
||||
m_hasPressedAction = true;
|
||||
return;
|
||||
}
|
||||
|
||||
m_hasPressedAction = false;
|
||||
QueueInput(MakePointerEvent(
|
||||
UIInputEventType::PointerButtonDown,
|
||||
m_mousePosition,
|
||||
UIPointerButton::Left));
|
||||
}
|
||||
|
||||
void HandleLeftButtonUp(float x, float y) {
|
||||
m_mousePosition = UIPoint(x, y);
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
UpdateHoveredAction(layout, x, y);
|
||||
if (const ButtonLayout* button = HitTestAction(layout, x, y)) {
|
||||
if (m_hasPressedAction && m_pressedAction == button->action) {
|
||||
ExecuteAction(button->action);
|
||||
}
|
||||
m_hasPressedAction = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_hasPressedAction = false;
|
||||
QueueInput(MakePointerEvent(
|
||||
UIInputEventType::PointerButtonUp,
|
||||
m_mousePosition,
|
||||
UIPointerButton::Left));
|
||||
}
|
||||
|
||||
void HandleKeyDown(std::int32_t keyCode) {
|
||||
QueueInput(MakeKeyEvent(keyCode));
|
||||
}
|
||||
|
||||
void OnResize(UINT width, UINT height) {
|
||||
if (width == 0u || height == 0u) {
|
||||
return;
|
||||
}
|
||||
m_renderer.Resize(width, height);
|
||||
}
|
||||
|
||||
void RenderFrame() {
|
||||
if (m_hwnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
|
||||
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
|
||||
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, m_shellMetrics);
|
||||
std::vector<UIInputEvent> events = std::move(m_pendingInputEvents);
|
||||
m_pendingInputEvents.clear();
|
||||
|
||||
m_frame = UpdateUIEditorObjectFieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
layout.fieldRect,
|
||||
events,
|
||||
m_fieldMetrics);
|
||||
ApplyInteractionResult(m_frame.result);
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorObjectFieldBasic");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), m_shellPalette.windowBackground);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.introRect,
|
||||
m_shellPalette,
|
||||
m_shellMetrics,
|
||||
"这个测试在验证什么功能?",
|
||||
"验证 Unity 风格 ObjectField 的值框、类型标签、clear / picker 按钮,以及 focus、activate、clear 契约。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 点击 value box 或 picker 按钮,应触发 activateRequested。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 点击 clear 按钮,或 focused 时按 Delete / Backspace,应清空当前对象。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. focused 后按 Enter / Space,应继续走 activate 契约。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
|
||||
"4. 重点检查 Hover、Focused、Has Value、Activate Count、Result 是否同步。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. 按 F12 或点击截图按钮,可导出当前窗口截图。",
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.controlRect,
|
||||
m_shellPalette,
|
||||
m_shellMetrics,
|
||||
"操作",
|
||||
"这里只保留当前场景需要的最小操作。");
|
||||
for (const ButtonLayout& button : layout.buttons) {
|
||||
DrawButton(
|
||||
drawList,
|
||||
button,
|
||||
m_shellPalette,
|
||||
m_shellMetrics,
|
||||
m_hasHoveredAction && m_hoveredAction == button.action);
|
||||
}
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.stateRect,
|
||||
m_shellPalette,
|
||||
m_shellMetrics,
|
||||
"状态摘要",
|
||||
"重点检查 hit、focus、value、activate、result。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 74.0f),
|
||||
"Hover: " + DescribeHitTarget(m_frame.result.hitTarget),
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 96.0f),
|
||||
std::string("Focused: ") + (m_interactionState.fieldState.focused ? "是" : "否"),
|
||||
m_interactionState.fieldState.focused ? m_shellPalette.textSuccess : m_shellPalette.textMuted,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f),
|
||||
std::string("Has Value: ") + (m_spec.hasValue ? "是" : "否"),
|
||||
m_spec.hasValue ? m_shellPalette.textSuccess : m_shellPalette.textMuted,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 140.0f),
|
||||
"Value: " + (m_spec.hasValue ? m_spec.objectName : m_spec.emptyText),
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 162.0f),
|
||||
"Type: " + (m_spec.objectTypeName.empty() ? std::string("(none)") : m_spec.objectTypeName),
|
||||
m_shellPalette.textMuted,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 184.0f),
|
||||
"Activate Count: " + std::to_string(m_activateCount),
|
||||
m_shellPalette.textPrimary,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 206.0f),
|
||||
"Result: " + m_lastResult,
|
||||
m_shellPalette.textMuted,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 232.0f),
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
? "截图排队中..."
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("F12 -> tests/UI/Editor/integration/shell/object_field_basic/captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary()),
|
||||
m_shellPalette.textWeak,
|
||||
m_shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.previewRect,
|
||||
m_shellPalette,
|
||||
m_shellMetrics,
|
||||
"ObjectField 预览",
|
||||
"这里只放一个 Unity 风格 ObjectField,不混入业务 Inspector。");
|
||||
AppendUIEditorObjectField(
|
||||
drawList,
|
||||
layout.fieldRect,
|
||||
m_spec,
|
||||
m_interactionState.fieldState,
|
||||
m_fieldPalette,
|
||||
m_fieldMetrics);
|
||||
|
||||
const bool framePresented = m_renderer.Render(drawData);
|
||||
m_autoScreenshot.CaptureIfRequested(
|
||||
m_renderer,
|
||||
drawData,
|
||||
static_cast<unsigned int>(width),
|
||||
static_cast<unsigned int>(height),
|
||||
framePresented);
|
||||
}
|
||||
|
||||
LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_CLOSE:
|
||||
DestroyWindow(hwnd);
|
||||
return 0;
|
||||
|
||||
case WM_DESTROY:
|
||||
m_hwnd = nullptr;
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
|
||||
case WM_SIZE:
|
||||
if (wParam != SIZE_MINIMIZED) {
|
||||
OnResize(static_cast<UINT>(LOWORD(lParam)), static_cast<UINT>(HIWORD(lParam)));
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
|
||||
case WM_MOUSEMOVE:
|
||||
HandleMouseMove(
|
||||
static_cast<float>(GET_X_LPARAM(lParam)),
|
||||
static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
return 0;
|
||||
|
||||
case WM_MOUSELEAVE:
|
||||
HandleMouseLeave();
|
||||
return 0;
|
||||
|
||||
case WM_LBUTTONDOWN:
|
||||
HandleLeftButtonDown(
|
||||
static_cast<float>(GET_X_LPARAM(lParam)),
|
||||
static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
return 0;
|
||||
|
||||
case WM_LBUTTONUP:
|
||||
HandleLeftButtonUp(
|
||||
static_cast<float>(GET_X_LPARAM(lParam)),
|
||||
static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
return 0;
|
||||
|
||||
case WM_SETFOCUS:
|
||||
QueueInput(MakeFocusEvent(UIInputEventType::FocusGained));
|
||||
return 0;
|
||||
|
||||
case WM_KILLFOCUS:
|
||||
QueueInput(MakeFocusEvent(UIInputEventType::FocusLost));
|
||||
return 0;
|
||||
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
if (wParam == VK_F12) {
|
||||
m_autoScreenshot.RequestCapture("manual_f12");
|
||||
m_lastResult = "截图已排队,输出到 captures/latest.png";
|
||||
return 0;
|
||||
}
|
||||
if (const std::int32_t keyCode = MapObjectFieldKey(static_cast<UINT>(wParam));
|
||||
keyCode != static_cast<std::int32_t>(KeyCode::None)) {
|
||||
HandleKeyDown(keyCode);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProcW(hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
HINSTANCE m_hInstance = nullptr;
|
||||
ATOM m_windowClassAtom = 0;
|
||||
NativeRenderer m_renderer = {};
|
||||
AutoScreenshotController m_autoScreenshot = {};
|
||||
std::filesystem::path m_captureRoot = {};
|
||||
XCEngine::Tests::EditorUI::EditorValidationShellPalette m_shellPalette = {};
|
||||
XCEngine::Tests::EditorUI::EditorValidationShellMetrics m_shellMetrics = {};
|
||||
XCEngine::UI::Editor::Widgets::UIEditorObjectFieldPalette m_fieldPalette = {};
|
||||
XCEngine::UI::Editor::Widgets::UIEditorObjectFieldMetrics m_fieldMetrics = {};
|
||||
UIEditorObjectFieldSpec m_spec = {};
|
||||
UIEditorObjectFieldInteractionState m_interactionState = {};
|
||||
UIEditorObjectFieldInteractionFrame m_frame = {};
|
||||
std::vector<UIInputEvent> m_pendingInputEvents = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_hoveredAction = ActionId::Reset;
|
||||
ActionId m_pressedAction = ActionId::Reset;
|
||||
bool m_hasHoveredAction = false;
|
||||
bool m_hasPressedAction = false;
|
||||
std::string m_lastResult = {};
|
||||
std::size_t m_activateCount = 0u;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
|
||||
return ScenarioApp().Run(hInstance, nCmdShow);
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorPanelContentHost.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceCompose.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Shell/UIEditorPanelContentHost.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceCompose.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -376,7 +376,7 @@ private:
|
||||
m_composeState = {};
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage =
|
||||
"当前先看 doc-a 与 inspector:两者都会 mount;切到 Console 后,tab body 会回退成 placeholder。";
|
||||
"默认状态下 doc-a 和 inspector 会挂到外部内容宿主,Console 仍然走占位内容路径。";
|
||||
UpdateComposeFrame();
|
||||
}
|
||||
|
||||
@@ -406,8 +406,8 @@ private:
|
||||
{ ActionId::ActivateInspector, "Activate Inspector", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + buttonHeight + rowGap, buttonWidth, buttonHeight), false },
|
||||
{ ActionId::CloseInspector, "Close Inspector", UIRect(buttonLeft, buttonTop + (buttonHeight + rowGap) * 2.0f, buttonWidth, buttonHeight), false },
|
||||
{ ActionId::OpenInspector, "Open Inspector", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + (buttonHeight + rowGap) * 2.0f, buttonWidth, buttonHeight), false },
|
||||
{ ActionId::Reset, "Reset", UIRect(buttonLeft, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false },
|
||||
{ ActionId::Capture, "Capture(F12)", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false }
|
||||
{ ActionId::Reset, "重置", UIRect(buttonLeft, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false },
|
||||
{ ActionId::Capture, "截图(F12)", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -443,7 +443,7 @@ private:
|
||||
m_autoScreenshot.RequestCapture("manual_button");
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage =
|
||||
"截图已排队,输出到 tests/UI/Editor/integration/shell/panel_content_host_basic/captures/。";
|
||||
"已请求截图,输出到 tests/UI/Editor/integration/shell/panel_content_host_basic/captures/。";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -507,19 +507,19 @@ private:
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("PanelContentHostBasic");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
|
||||
|
||||
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 Editor panel content host contract,不做业务逻辑。");
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 72.0f), "1. 验证 HostedContent panel 会正式接管 DockHost body,而不是继续画 placeholder。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 94.0f), "2. 验证 tab 切换时,旧 body unmount,新 body mount。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. 验证切到 Console 这种 placeholder panel 后,external host 会退出。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 138.0f), "4. 验证 standalone HostedContent close/open 时,会发生 unmount / remount。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 164.0f), "建议操作:Doc A -> Doc B -> Console -> Open/Close Inspector,观察 Mounted Panels 和 Events。", kTextWeak, 11.0f);
|
||||
DrawCard(drawList, m_introRect, "这个测试验证什么功能?", "验证面板内容宿主在 DockHost 中的挂载、卸载和占位回退契约,不做业务逻辑。");
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 72.0f), "1. HostedContent panel 应挂到 DockHost 外部 body,不再走占位内容。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 94.0f), "2. 切换 tab 时,旧 body 应卸载,新 body 应挂载。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. Console 仍然是占位面板,不应该进入 external host。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 138.0f), "4. 关闭再打开 inspector,应看到卸载和重新挂载事件。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 164.0f), "建议顺序:Doc A -> Doc B -> Console -> Close/Open Inspector。", kTextWeak, 11.0f);
|
||||
|
||||
DrawCard(drawList, m_controlsRect, "操作", "只保留内容承载 contract 必要按钮。");
|
||||
DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前契约验证需要的按钮。");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
DrawButton(drawList, button);
|
||||
}
|
||||
|
||||
DrawCard(drawList, m_stateRect, "状态", "重点检查 mounted panel 集合和本帧 mount events。");
|
||||
DrawCard(drawList, m_stateRect, "状态", "重点检查已挂载面板集合和本帧挂载事件。");
|
||||
float stateY = m_stateRect.y + 66.0f;
|
||||
auto addStateLine = [&](std::string label, std::string value, const UIColor& color, float fontSize = 12.0f) {
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 18.0f, stateY), std::move(label) + ": " + std::move(value), color, fontSize);
|
||||
@@ -539,16 +539,16 @@ private:
|
||||
addStateLine("doc-b", DescribeMountedState(m_composeFrame, "doc-b"), kTextWeak, 11.0f);
|
||||
addStateLine("inspector", DescribeMountedState(m_composeFrame, "inspector"), kTextWeak, 11.0f);
|
||||
addStateLine(
|
||||
"Screenshot",
|
||||
"截图",
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
? "截图排队中..."
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("F12 或 Capture -> captures/")
|
||||
? std::string("F12 或点按钮 -> captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary()),
|
||||
kTextWeak,
|
||||
11.0f);
|
||||
|
||||
DrawCard(drawList, m_previewRect, "Preview", "DockHost 画壳;蓝色内容块是 external content host 实际挂进去的 body。");
|
||||
DrawCard(drawList, m_previewRect, "预览", "DockHost 只画外壳;蓝色内容块表示外部内容宿主实际挂入的 body。");
|
||||
AppendUIEditorWorkspaceCompose(drawList, m_composeFrame);
|
||||
for (const UIEditorPanelContentHostPanelState& panelState : m_composeFrame.contentHostFrame.panelStates) {
|
||||
if (!panelState.mounted ||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorPanelFrame.h>
|
||||
#include <XCEditor/Shell/UIEditorPanelFrame.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -375,7 +375,7 @@ private:
|
||||
{ ActionId::ToggleFooter, "Footer", UIRect(left, top, width, buttonHeight), m_state.showFooter },
|
||||
{ ActionId::TogglePin, "Pin Button", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_state.pinnable },
|
||||
{ ActionId::ToggleClose, "Close Button", UIRect(left, top + (buttonHeight + gap) * 2.0f, width, buttonHeight), m_state.closable },
|
||||
{ ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false }
|
||||
{ ActionId::Reset, "重置", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -405,25 +405,30 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
introRect,
|
||||
"测试功能:PanelFrame 基础壳层",
|
||||
"重点检查:Header / Body / Footer 布局,Active / Focus 边框,Pin / Close 命中。");
|
||||
"这个测试验证什么功能?",
|
||||
"验证 PanelFrame 的 header/body/footer 分区,以及 active、focus、pin、close 命中。");
|
||||
drawList.AddText(
|
||||
UIPoint(introRect.x + 16.0f, introRect.y + 66.0f),
|
||||
"操作:移动鼠标观察 hover;点 Header 切 Active;点 Body/Footer 取 Focus;点 Pin/Close 看 Result;按 F12 截图。",
|
||||
"1. hover 各区域,检查命中 part 是否正确。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(introRect.x + 16.0f, introRect.y + 90.0f),
|
||||
"这个场景只验证 Editor 基础层 PanelFrame,不包含任何业务面板。",
|
||||
"2. 点击 Header、Body、Footer、Pin、Close,检查状态变化。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(introRect.x + 16.0f, introRect.y + 114.0f),
|
||||
"3. 右侧只放一个 PanelFrame,不接业务面板。",
|
||||
kTextWeak,
|
||||
12.0f);
|
||||
|
||||
DrawCard(drawList, controlsRect, "开关", "这里只保留基础状态开关,避免试验面板过杂。");
|
||||
DrawCard(drawList, controlsRect, "操作", "只保留影响 PanelFrame contract 的开关。");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
DrawButton(drawList, button);
|
||||
}
|
||||
|
||||
DrawCard(drawList, stateRect, "状态", "右侧面板的命中结果和状态变化会同步显示在这里。");
|
||||
DrawCard(drawList, stateRect, "状态摘要", "重点看 hover、active、focus、pinned 和结果。");
|
||||
drawList.AddText(
|
||||
UIPoint(stateRect.x + 16.0f, stateRect.y + 66.0f),
|
||||
"Hover: " + DescribePart(m_hoveredPart),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorPropertyGridInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Widgets/UIEditorPropertyGrid.h>
|
||||
#include <XCEditor/Fields/UIEditorPropertyGridInteraction.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorPropertyGrid.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -57,7 +57,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridFieldKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridSection;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorPropertyGridBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | PropertyGrid Basic";
|
||||
@@ -91,11 +90,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
@@ -543,15 +537,6 @@ private:
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/property_grid_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad =
|
||||
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
return true;
|
||||
}
|
||||
@@ -579,7 +564,7 @@ private:
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
@@ -606,7 +591,7 @@ private:
|
||||
}
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics();
|
||||
m_gridFrame =
|
||||
UpdateUIEditorPropertyGridInteraction(
|
||||
m_interactionState,
|
||||
@@ -741,7 +726,7 @@ private:
|
||||
|
||||
UIEditorPropertyGridInteractionResult PumpGridEvents(std::vector<UIInputEvent> events) {
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics();
|
||||
m_gridFrame =
|
||||
UpdateUIEditorPropertyGridInteraction(
|
||||
m_interactionState,
|
||||
@@ -855,17 +840,17 @@ private:
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
RefreshGridFrame();
|
||||
|
||||
const UIEditorPropertyGridHitTarget currentHit =
|
||||
HitTestUIEditorPropertyGrid(m_gridFrame.layout, m_mousePosition);
|
||||
const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(m_theme);
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
|
||||
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme);
|
||||
const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette(m_theme);
|
||||
const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics();
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
|
||||
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics();
|
||||
const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette();
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorPropertyGridBasic");
|
||||
@@ -987,7 +972,7 @@ private:
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
"Style: fixed",
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
@@ -1037,14 +1022,11 @@ private:
|
||||
UIExpansionModel m_expansionModel = {};
|
||||
UIPropertyEditModel m_propertyEditModel = {};
|
||||
UIEditorPropertyGridInteractionState m_interactionState = {};
|
||||
UIEditorPropertyGridInteractionFrame m_gridFrame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
UIEditorPropertyGridInteractionFrame m_gridFrame = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_hoveredAction = ActionId::Reset;
|
||||
bool m_hasHoveredAction = false;
|
||||
std::string m_lastResult = {};
|
||||
std::string m_lastCommit = {};
|
||||
std::string m_themeStatus = "fallback";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorScrollViewInteraction.h>
|
||||
#include <XCEditor/Widgets/UIEditorScrollView.h>
|
||||
#include <XCEditor/Collections/UIEditorScrollViewInteraction.h>
|
||||
#include <XCEditor/Collections/UIEditorScrollView.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -194,7 +194,7 @@ std::string DescribeHitTarget(const UIEditorScrollViewHitTarget& hitTarget) {
|
||||
return "scrollbar-thumb";
|
||||
case UIEditorScrollViewHitTargetKind::None:
|
||||
default:
|
||||
return "无";
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ private:
|
||||
m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
m_hoveredAction = ActionId::Reset;
|
||||
m_hasHoveredAction = false;
|
||||
m_lastResult = "已重置到 " + std::to_string(m_logLines.size()) + " 行默认滚动位置";
|
||||
m_lastResult = "已重置到 " + std::to_string(m_logLines.size()) + " 行默认滚动内容";
|
||||
RefreshScrollFrame();
|
||||
}
|
||||
|
||||
@@ -478,7 +478,7 @@ private:
|
||||
const UIEditorScrollViewInteractionResult result =
|
||||
PumpScrollEvents({ MakeWheelEvent(m_mousePosition, wheelDelta) });
|
||||
if (result.offsetChanged) {
|
||||
m_lastResult = "滚轮滚动: offset 已更新";
|
||||
m_lastResult = "滚轮滚动:offset 已更新";
|
||||
}
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
}
|
||||
@@ -568,23 +568,23 @@ private:
|
||||
}
|
||||
|
||||
if (result.offsetChanged) {
|
||||
m_lastResult = "滚动位置变化";
|
||||
m_lastResult = "滚动位置已变化";
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.focusChanged) {
|
||||
m_lastResult = m_interactionState.scrollViewState.focused
|
||||
? "ScrollView 获得 focus"
|
||||
? "ScrollView 已获得 focus"
|
||||
: "ScrollView focus 已清除";
|
||||
return;
|
||||
}
|
||||
|
||||
if (insideScrollView) {
|
||||
m_lastResult = "点击内容区域: 只验证 focus / hover / scrollbar";
|
||||
m_lastResult = "点击内容区:只验证 focus / hover / scrollbar";
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastResult = "等待交互";
|
||||
m_lastResult = "无变化";
|
||||
}
|
||||
|
||||
void ExecuteAction(ActionId action) {
|
||||
@@ -622,31 +622,31 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.introRect,
|
||||
"这个测试在验证什么功能",
|
||||
"只验证 Editor ScrollView 基础控件,不涉及任何业务面板。");
|
||||
"这个测试验证什么功能?",
|
||||
"验证滚动视图的滚轮滚动、thumb 拖拽、focus 切换和 offset clamp。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 把鼠标放到右侧日志区内滚动滚轮:内容应上下移动,offset 应变化。",
|
||||
"1. 在内容区滚轮滚动,检查 offset 是否连续更新。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 继续滚到边界后 offset 要被 clamp,不能越界。",
|
||||
"2. 滚到底部后继续滚动,offset 必须被 clamp。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. 拖拽 scrollbar thumb,内容位置应同步变化。",
|
||||
"3. 拖拽 scrollbar thumb,检查 offset 与 thumb 位置是否同步。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
|
||||
"4. 点击内容区只改变 focus;点击外部空白后 focus 应清除。",
|
||||
"4. 点击内容区只更新 focus / hover;点击外部应能清掉 focus。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可自动截图。",
|
||||
"5. 按 F12 或设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 触发截图。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
|
||||
@@ -666,7 +666,7 @@ private:
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f),
|
||||
std::string("Focused: ") + (m_interactionState.scrollViewState.focused ? "开" : "关"),
|
||||
std::string("Focused: ") + (m_interactionState.scrollViewState.focused ? "是" : "否"),
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorStatusBar.h>
|
||||
#include <XCEditor/Shell/UIEditorStatusBar.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -313,10 +313,10 @@ private:
|
||||
if (m_hoverTarget.kind == UIEditorStatusBarHitTargetKind::Segment) {
|
||||
m_state.activeIndex = m_hoverTarget.index;
|
||||
m_state.focused = true;
|
||||
m_lastResult = "激活 segment: " + m_segments[m_hoverTarget.index].segmentId;
|
||||
m_lastResult = "命中 segment: " + m_segments[m_hoverTarget.index].segmentId;
|
||||
} else if (m_hoverTarget.kind == UIEditorStatusBarHitTargetKind::Background) {
|
||||
m_state.activeIndex = UIEditorStatusBarInvalidIndex;
|
||||
m_lastResult = "点击 status bar background";
|
||||
m_lastResult = "命中 status bar background";
|
||||
} else {
|
||||
m_lastResult = "命中 " + DescribeHitTarget(m_hoverTarget);
|
||||
}
|
||||
@@ -330,22 +330,22 @@ private:
|
||||
m_segments[1].tone == UIEditorStatusBarTextTone::Accent
|
||||
? UIEditorStatusBarTextTone::Primary
|
||||
: UIEditorStatusBarTextTone::Accent;
|
||||
m_lastResult = "切换 Selection 文本强调";
|
||||
m_lastResult = "Selection 强调已切换";
|
||||
break;
|
||||
case ActionId::ToggleSeparator:
|
||||
m_segments[0].showSeparator = !m_segments[0].showSeparator;
|
||||
m_lastResult = m_segments[0].showSeparator ? "开启 Leading separator" : "关闭 Leading separator";
|
||||
m_lastResult = m_segments[0].showSeparator ? "Leading separator 已开启" : "Leading separator 已关闭";
|
||||
break;
|
||||
case ActionId::MoveToTrailing:
|
||||
m_segments[1].slot =
|
||||
m_segments[1].slot == UIEditorStatusBarSlot::Leading
|
||||
? UIEditorStatusBarSlot::Trailing
|
||||
: UIEditorStatusBarSlot::Leading;
|
||||
m_lastResult = "切换 Selection slot -> " + DescribeSlot(m_segments[1].slot);
|
||||
m_lastResult = "Selection slot -> " + DescribeSlot(m_segments[1].slot);
|
||||
break;
|
||||
case ActionId::Reset:
|
||||
ResetState();
|
||||
m_lastResult = "状态重置";
|
||||
m_lastResult = "状态已重置";
|
||||
break;
|
||||
case ActionId::Capture:
|
||||
m_autoScreenshot.RequestCapture("manual_button");
|
||||
@@ -368,7 +368,7 @@ private:
|
||||
m_state.focused = true;
|
||||
m_state.activeIndex = 1u;
|
||||
m_hoverTarget = {};
|
||||
m_lastResult = "Ready";
|
||||
m_lastResult = "就绪";
|
||||
}
|
||||
|
||||
void UpdateHover() {
|
||||
@@ -383,11 +383,11 @@ private:
|
||||
const float buttonHeight = 34.0f;
|
||||
const float gap = 10.0f;
|
||||
m_buttons = {
|
||||
{ ActionId::ToggleAccent, "强调文本", UIRect(left, top, width, buttonHeight), m_segments[1].tone == UIEditorStatusBarTextTone::Accent },
|
||||
{ ActionId::ToggleSeparator, "Leading 分隔线", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_segments[0].showSeparator },
|
||||
{ ActionId::ToggleAccent, "切换强调", UIRect(left, top, width, buttonHeight), m_segments[1].tone == UIEditorStatusBarTextTone::Accent },
|
||||
{ ActionId::ToggleSeparator, "Leading separator", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_segments[0].showSeparator },
|
||||
{ ActionId::MoveToTrailing, "切换 Selection Slot", UIRect(left, top + (buttonHeight + gap) * 2.0f, width, buttonHeight), m_segments[1].slot == UIEditorStatusBarSlot::Trailing },
|
||||
{ ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false },
|
||||
{ ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false }
|
||||
{ ActionId::Reset, "重置", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false },
|
||||
{ ActionId::Capture, "截图(F12)", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -429,25 +429,30 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
introRect,
|
||||
"测试功能:StatusBar 基础壳层",
|
||||
"重点检查:Leading / Trailing slot 对齐,文本强调,separator 开关,hover / active 命中。");
|
||||
"这个测试验证什么功能?",
|
||||
"验证 StatusBar 的 leading/trailing 布局、separator、强调文本,以及 hover/active 命中。");
|
||||
drawList.AddText(
|
||||
UIPoint(introRect.x + 16.0f, introRect.y + 66.0f),
|
||||
"操作:hover 观察 segment 高亮;点击 segment 切 active;切换左侧按钮检查 slot / separator / emphasis;按 F12 或点“截图”。",
|
||||
"1. hover 不同 segment / separator,检查命中是否正确。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(introRect.x + 16.0f, introRect.y + 90.0f),
|
||||
"这个场景只验证 Editor 基础层 StatusBar,不混入任何业务面板。",
|
||||
"2. 点击 segment,检查 active 是否切到对应项。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(introRect.x + 16.0f, introRect.y + 114.0f),
|
||||
"3. 切换强调、separator 和 Selection slot,检查布局是否稳定。",
|
||||
kTextWeak,
|
||||
12.0f);
|
||||
|
||||
DrawCard(drawList, controlsRect, "开关", "只保留和当前 StatusBar contract 直接相关的操作。");
|
||||
DrawCard(drawList, controlsRect, "操作", "只保留与 StatusBar contract 直接相关的开关。");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
DrawButton(drawList, button);
|
||||
}
|
||||
|
||||
DrawCard(drawList, stateRect, "状态", "命中、active、slot 和结果统一回显在这里。");
|
||||
DrawCard(drawList, stateRect, "状态摘要", "重点看 hover、active、Selection slot、separator 和结果。");
|
||||
drawList.AddText(
|
||||
UIPoint(stateRect.x + 16.0f, stateRect.y + 66.0f),
|
||||
"Hover: " + DescribeHitTarget(m_hoverTarget),
|
||||
@@ -489,12 +494,12 @@ private:
|
||||
drawList.AddRectOutline(viewportRect, kCardBorder, 1.0f, 10.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(viewportRect.x + 18.0f, viewportRect.y + 18.0f),
|
||||
"Preview Host",
|
||||
"预览宿主",
|
||||
kTextPrimary,
|
||||
18.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(viewportRect.x + 18.0f, viewportRect.y + 48.0f),
|
||||
"这里只保留一个空白宿主区域,专门看底部 StatusBar 的对齐和交互。",
|
||||
"这里只放一个宿主区域,用来观察底部 StatusBar 的布局和状态变化。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorTabStripInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorPanelRegistry.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Widgets/UIEditorTabStrip.h>
|
||||
#include <XCEditor/Collections/UIEditorTabStripInteraction.h>
|
||||
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Collections/UIEditorTabStrip.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -79,7 +79,6 @@ constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f);
|
||||
constexpr UIColor kTextMuted(0.73f, 0.73f, 0.73f, 1.0f);
|
||||
constexpr UIColor kTextWeak(0.58f, 0.58f, 0.58f, 1.0f);
|
||||
constexpr UIColor kSuccess(0.62f, 0.74f, 0.62f, 1.0f);
|
||||
constexpr UIColor kWarning(0.78f, 0.70f, 0.46f, 1.0f);
|
||||
constexpr UIColor kDanger(0.82f, 0.48f, 0.48f, 1.0f);
|
||||
constexpr UIColor kButtonBg(0.25f, 0.25f, 0.25f, 1.0f);
|
||||
constexpr UIColor kButtonHoveredBg(0.31f, 0.31f, 0.31f, 1.0f);
|
||||
@@ -402,7 +401,7 @@ private:
|
||||
m_tabStripFrame = {};
|
||||
m_tabItems.clear();
|
||||
m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
m_lastResult = "等待操作";
|
||||
m_lastResult = "已重置到默认标签状态";
|
||||
}
|
||||
|
||||
UIRect GetTabStripRect() const {
|
||||
@@ -524,7 +523,7 @@ private:
|
||||
|
||||
if (result.hitTarget.kind == UIEditorTabStripHitTargetKind::HeaderBackground ||
|
||||
result.hitTarget.kind == UIEditorTabStripHitTargetKind::Content) {
|
||||
m_lastResult = "TabStrip 获得 focus";
|
||||
m_lastResult = "命中 TabStrip 背景,保留 focus";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -577,7 +576,6 @@ private:
|
||||
m_tabItems.push_back(std::move(item));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RenderFrame() {
|
||||
@@ -610,30 +608,30 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
introRect,
|
||||
"测试功能:Editor TabStrip 基础层",
|
||||
"只验证 TabStrip header / hit-test / selection / close / keyboard,不包含任何业务面板。");
|
||||
"这个测试验证什么功能?",
|
||||
"验证 TabStrip 的 header 命中、选中切换、关闭请求和键盘导航,不接业务面板。");
|
||||
drawList.AddText(
|
||||
UIPoint(introRect.x + 16.0f, introRect.y + 68.0f),
|
||||
"重点检查:tab 布局是否整齐;selected / hover / focus 是否正确;Close 后 active panel 是否回退正确。",
|
||||
"1. 点击 tab,检查 selected / active panel 是否同步。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(introRect.x + 16.0f, introRect.y + 92.0f),
|
||||
"操作:点击 tab 切换;点击 X 关闭;Document C 没有 X;点击内容区后按 Left / Right / Home / End;Reset 恢复;F12 截图。",
|
||||
"2. 点击 X,只验证关闭请求;Document C 没有关闭按钮。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(introRect.x + 16.0f, introRect.y + 116.0f),
|
||||
"预期:Close 命中优先于 tab;键盘导航只在 focused 时生效;Document C 始终保留。",
|
||||
"3. Left / Right / Home / End 验证键盘导航;按重置恢复初始状态。",
|
||||
kTextWeak,
|
||||
12.0f);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
stateRect,
|
||||
"状态回显",
|
||||
"这里直接回显 hover / focus / active panel / result,方便人工检查。");
|
||||
DrawButton(drawList, m_resetButtonRect, "Reset", m_resetButtonHovered);
|
||||
"状态摘要",
|
||||
"持续显示 hover、focus、active panel、tabs、result 和校验结果。");
|
||||
DrawButton(drawList, m_resetButtonRect, "重置", m_resetButtonHovered);
|
||||
|
||||
const std::size_t selectedIndex =
|
||||
ResolveUIEditorTabStripSelectedIndex(m_tabItems, m_controller.GetWorkspace().activePanelId);
|
||||
@@ -676,7 +674,7 @@ private:
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(stateRect.x + 16.0f, stateRect.y + 276.0f),
|
||||
validation.IsValid() ? "Validation: OK" : "Validation: " + validation.message,
|
||||
validation.IsValid() ? "校验: OK" : "校验: " + validation.message,
|
||||
validation.IsValid() ? kSuccess : kDanger,
|
||||
12.0f);
|
||||
|
||||
@@ -696,7 +694,7 @@ private:
|
||||
drawList,
|
||||
previewCardRect,
|
||||
"预览区",
|
||||
"这里只放一个 TabStrip 和一个 content placeholder,避免试验面板过杂。");
|
||||
"这里只放一个 TabStrip 和一个 content placeholder,用来观察 header 与 content frame。");
|
||||
|
||||
AppendUIEditorTabStripBackground(drawList, m_layout, m_tabStripState);
|
||||
AppendUIEditorTabStripForeground(drawList, m_layout, m_tabItems, m_tabStripState);
|
||||
@@ -716,12 +714,12 @@ private:
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_layout.contentRect.x + 20.0f, m_layout.contentRect.y + 76.0f),
|
||||
"这个区域只用于验证 TabStrip content frame 与 focus,不承载业务内容。",
|
||||
"这里只验证 TabStrip 的 content frame 与 focus 同步,不接业务内容。",
|
||||
kTextWeak,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_layout.contentRect.x + 20.0f, m_layout.contentRect.y + 100.0f),
|
||||
"检查点:关闭 Document B 后,应自动回退到相邻 tab;Document C 无法关闭。",
|
||||
"可点击 Document B 切换,或点击 Document C 验证不可关闭 tab。",
|
||||
kTextWeak,
|
||||
12.0f);
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorTextFieldInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Widgets/UIEditorTextField.h>
|
||||
#include <XCEditor/Fields/UIEditorTextFieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Fields/UIEditorTextField.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -25,6 +26,8 @@
|
||||
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
|
||||
#endif
|
||||
|
||||
// 场景说明:验证固定样式下 TextField 的基础编辑、提交、取消与焦点切换。
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
@@ -46,7 +49,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorTextField;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldSpec;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorTextFieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | TextField Basic";
|
||||
@@ -83,10 +85,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
@@ -157,18 +155,16 @@ ScenarioLayout BuildScenarioLayout(
|
||||
return layout;
|
||||
}
|
||||
|
||||
XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics ResolveHostedTextFieldMetrics(
|
||||
const Style::UITheme& theme) {
|
||||
const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(theme);
|
||||
const auto textMetrics = XCEngine::UI::Editor::ResolveUIEditorTextFieldMetrics(theme);
|
||||
return XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics(propertyMetrics, textMetrics);
|
||||
XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics ResolvePropertyGridTextFieldMetrics() {
|
||||
return XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics(
|
||||
XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(),
|
||||
XCEngine::UI::Editor::ResolveUIEditorTextFieldMetrics());
|
||||
}
|
||||
|
||||
XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette ResolveHostedTextFieldPalette(
|
||||
const Style::UITheme& theme) {
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(theme);
|
||||
const auto textPalette = XCEngine::UI::Editor::ResolveUIEditorTextFieldPalette(theme);
|
||||
return XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette(propertyPalette, textPalette);
|
||||
XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette ResolvePropertyGridTextFieldPalette() {
|
||||
return XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette(
|
||||
XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(),
|
||||
XCEngine::UI::Editor::ResolveUIEditorTextFieldPalette());
|
||||
}
|
||||
|
||||
void DrawCard(
|
||||
@@ -427,14 +423,6 @@ private:
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/text_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad =
|
||||
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
return true;
|
||||
@@ -463,7 +451,7 @@ private:
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
@@ -487,7 +475,7 @@ private:
|
||||
}
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = ResolveHostedTextFieldMetrics(m_theme);
|
||||
const auto metrics = ResolvePropertyGridTextFieldMetrics();
|
||||
m_frame = UpdateUIEditorTextFieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -605,7 +593,7 @@ private:
|
||||
|
||||
UIEditorTextFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = ResolveHostedTextFieldMetrics(m_theme);
|
||||
const auto metrics = ResolvePropertyGridTextFieldMetrics();
|
||||
m_frame = UpdateUIEditorTextFieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -661,16 +649,16 @@ private:
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
RefreshFrame();
|
||||
|
||||
const UIEditorTextFieldHitTarget currentHit =
|
||||
HitTestUIEditorTextField(m_frame.layout, m_mousePosition);
|
||||
const auto textMetrics = ResolveHostedTextFieldMetrics(m_theme);
|
||||
const auto textPalette = ResolveHostedTextFieldPalette(m_theme);
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
|
||||
const auto textMetrics = ResolvePropertyGridTextFieldMetrics();
|
||||
const auto textPalette = ResolvePropertyGridTextFieldPalette();
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorTextFieldBasic");
|
||||
@@ -681,8 +669,8 @@ private:
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试验证什么功能",
|
||||
"验证 UIEditorTextField 的基础编辑交互契约,不涉及 PropertyGrid 或业务 Inspector。");
|
||||
"这个测试在验证什么功能?",
|
||||
"验证 UIEditorTextField 的基础编辑契约,不涉及 PropertyGrid 或任何 Inspector 业务逻辑。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 点击 value box,检查是否进入编辑态。",
|
||||
@@ -775,7 +763,7 @@ private:
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
"Style: fixed",
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
@@ -785,7 +773,7 @@ private:
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"TextField 预览",
|
||||
"这里只放一个 Editor TextField,用来验证基础字段行为。");
|
||||
"这里只放一个固定样式 TextField,用来验证基础字段行为。");
|
||||
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
|
||||
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
|
||||
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
|
||||
@@ -827,12 +815,10 @@ private:
|
||||
UIEditorTextFieldSpec m_spec = {};
|
||||
UIEditorTextFieldInteractionState m_interactionState = {};
|
||||
UIEditorTextFieldInteractionFrame m_frame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_hoveredAction = ActionId::Reset;
|
||||
bool m_hasHoveredAction = false;
|
||||
std::string m_lastResult = {};
|
||||
std::string m_themeStatus = "fallback";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -684,8 +684,8 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.introRect,
|
||||
"这个测试在验证什么功能?",
|
||||
"只验证 Editor TreeView 的单选、层级展开/折叠和键盘导航契约,不涉及任何业务面板。");
|
||||
"这个测试验证什么功能?",
|
||||
"只验证 Editor TreeView 的单选、层级展开/折叠和键盘导航。不涉及任何业务面板。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 点击 row:只切换 selection,hover / selected / focused 必须能明确区分。",
|
||||
@@ -708,7 +708,7 @@ private:
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. 点击树外空白清除 focus;F12 手动截图;XCUI_AUTO_CAPTURE_ON_STARTUP=1 自动截图。",
|
||||
"5. 点击树外空白清除 focus;F12 手动截图;XCUI_AUTO_CAPTURE_ON_STARTUP=1 可启动自动截图。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <XCEditor/Collections/UIEditorInlineRenameSession.h>
|
||||
#include <XCEditor/Collections/UIEditorTreeView.h>
|
||||
#include <XCEditor/Collections/UIEditorTreeViewInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Fields/UIEditorTextField.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
@@ -13,7 +14,6 @@
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEngine/UI/Style/Theme.h>
|
||||
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
|
||||
#include <XCEngine/UI/Widgets/UISelectionModel.h>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
|
||||
#endif
|
||||
|
||||
// 场景说明:验证固定样式下 TreeView 的 inline rename 启动、提交、取消与外部点击提交。
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
@@ -46,8 +47,8 @@ using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Widgets::UIExpansionModel;
|
||||
using XCEngine::UI::Widgets::UISelectionModel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics;
|
||||
using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette;
|
||||
using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics;
|
||||
using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette;
|
||||
using XCEngine::UI::Editor::BuildUIEditorInlineRenameTextFieldMetrics;
|
||||
using XCEngine::UI::Editor::Host::AutoScreenshotController;
|
||||
using XCEngine::UI::Editor::Host::NativeRenderer;
|
||||
@@ -75,8 +76,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorTreeViewInvalidIndex;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTreeViewItem;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorTreeViewInlineRenameValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | TreeView Inline Rename";
|
||||
|
||||
@@ -93,7 +92,6 @@ struct ScenarioLayout {
|
||||
};
|
||||
|
||||
std::filesystem::path ResolveRepoRootPath();
|
||||
std::filesystem::path ResolveValidationThemePath();
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y);
|
||||
std::int32_t MapTreeKey(UINT keyCode);
|
||||
ScenarioLayout BuildScenarioLayout(float width, float height, const EditorValidationShellMetrics& shellMetrics);
|
||||
@@ -143,8 +141,6 @@ private:
|
||||
NativeRenderer m_renderer = {};
|
||||
AutoScreenshotController m_autoScreenshot = {};
|
||||
std::filesystem::path m_captureRoot = {};
|
||||
Style::UITheme m_theme = {};
|
||||
std::string m_themeStatus = "fallback";
|
||||
std::vector<UIEditorTreeViewItem> m_items = {};
|
||||
UISelectionModel m_selectionModel = {};
|
||||
UIExpansionModel m_expansionModel = {};
|
||||
@@ -168,10 +164,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme").lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height;
|
||||
}
|
||||
@@ -345,13 +337,6 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
}
|
||||
m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/tree_view_inline_rename/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad = XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
ResetScenario();
|
||||
return true;
|
||||
}
|
||||
@@ -374,7 +359,7 @@ ScenarioLayout ScenarioApp::GetLayout() const {
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
return BuildScenarioLayout(width, height, XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
return BuildScenarioLayout(width, height, XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
void ScenarioApp::ResetScenario() {
|
||||
@@ -718,9 +703,9 @@ UIEditorInlineRenameSessionRequest ScenarioApp::BuildRenameRequest(bool beginSes
|
||||
}
|
||||
|
||||
UIEditorTextFieldMetrics ScenarioApp::ResolveHostedTextFieldMetrics() const {
|
||||
const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics(m_theme);
|
||||
const auto textMetrics = ResolveUIEditorTextFieldMetrics(m_theme);
|
||||
return BuildUIEditorHostedTextFieldMetrics(propertyMetrics, textMetrics);
|
||||
const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics();
|
||||
const auto textMetrics = ResolveUIEditorTextFieldMetrics();
|
||||
return BuildUIEditorPropertyGridTextFieldMetrics(propertyMetrics, textMetrics);
|
||||
}
|
||||
|
||||
UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& bounds) const {
|
||||
@@ -728,9 +713,9 @@ UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& b
|
||||
}
|
||||
|
||||
UIEditorTextFieldPalette ScenarioApp::ResolveInlineRenamePalette() const {
|
||||
const auto propertyPalette = ResolveUIEditorPropertyGridPalette(m_theme);
|
||||
const auto textPalette = ResolveUIEditorTextFieldPalette(m_theme);
|
||||
return BuildUIEditorHostedTextFieldPalette(propertyPalette, textPalette);
|
||||
const auto propertyPalette = ResolveUIEditorPropertyGridPalette();
|
||||
const auto textPalette = ResolveUIEditorTextFieldPalette();
|
||||
return BuildUIEditorPropertyGridTextFieldPalette(propertyPalette, textPalette);
|
||||
}
|
||||
|
||||
void ScenarioApp::ExecuteAction(ActionId action) {
|
||||
@@ -755,9 +740,9 @@ void ScenarioApp::RenderFrame() {
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics =
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette =
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
RefreshTreeFrame();
|
||||
|
||||
@@ -781,8 +766,8 @@ void ScenarioApp::RenderFrame() {
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试在验证什么功能?",
|
||||
"只验证 Editor TreeView 的 inline rename 契约:默认进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。");
|
||||
"这个测试验证什么功能?",
|
||||
"只验证 Editor TreeView 的 inline rename:默认进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 启动后默认进入 hero-mesh rename;输入框只能覆盖 label 区。",
|
||||
@@ -790,7 +775,7 @@ void ScenarioApp::RenderFrame() {
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 输入字符后,Draft 必须实时变化,原标签不能和 overlay 叠字。",
|
||||
"2. 输入字符后,Draft 必须实时变化;原标签不能和 overlay 叠字。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
@@ -805,7 +790,7 @@ void ScenarioApp::RenderFrame() {
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. rename 中点击输入框外部:提交当前草稿并退出编辑态。",
|
||||
"5. rename 中点击输入框外部:提交当前草稿,并退出编辑态。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
@@ -903,10 +888,9 @@ void ScenarioApp::RenderFrame() {
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 334.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
"Style: fixed",
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.previewRect,
|
||||
|
||||
@@ -810,7 +810,7 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.introRect,
|
||||
"这个测试在验证什么功能?",
|
||||
"这个测试验证什么功能?",
|
||||
"只验证 Editor TreeView 的多选契约,不混入 Hierarchy / Inspector 业务面板。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
@@ -819,12 +819,12 @@ private:
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. Ctrl+单击 / Shift+单击:应只验证 visible tree 范围内的多选切换与连续扩选。",
|
||||
"2. Ctrl+单击 / Shift+单击:只验证 visible tree 范围内的多选切换与连续扩选。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. 点击 disclosure:应只切换 expanded,不应无故打散 selection。",
|
||||
"3. 点击 disclosure:只切换 expanded,不应无故打散 selection。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
@@ -853,7 +853,7 @@ private:
|
||||
DrawButton(drawList, button, m_hasHoveredAction && m_hoveredAction == button.action);
|
||||
}
|
||||
|
||||
DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 multi-select 与 expanded / visible contract 是否稳定。");
|
||||
DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 multi-select 与 expanded / visible 契约是否稳定。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f),
|
||||
"Hit: " + DescribeHitTarget(currentHit, m_items),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Core/UIEditorVector2FieldInteraction.h>
|
||||
#include <XCEditor/Widgets/UIEditorVector2Field.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorVector2FieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorVector2Field.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -48,8 +48,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldInvalidComponentIndex;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldSpec;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorVector2FieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Vector2Field Basic";
|
||||
|
||||
@@ -85,11 +83,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
@@ -437,14 +430,6 @@ private:
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector2_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad =
|
||||
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
return true;
|
||||
@@ -473,7 +458,7 @@ private:
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
@@ -503,7 +488,7 @@ private:
|
||||
}
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics();
|
||||
m_frame = UpdateUIEditorVector2FieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -623,7 +608,7 @@ private:
|
||||
|
||||
UIEditorVector2FieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics();
|
||||
m_frame = UpdateUIEditorVector2FieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -635,12 +620,12 @@ private:
|
||||
|
||||
void UpdateResultText(const UIEditorVector2FieldInteractionResult& result) {
|
||||
if (result.editCommitRejected) {
|
||||
m_lastResult = "提交失败,当前文本不是合法数字";
|
||||
m_lastResult = "提交被拒绝:当前输入不是合法数字";
|
||||
return;
|
||||
}
|
||||
if (result.editCommitted) {
|
||||
m_lastResult =
|
||||
std::string("已提交 ") +
|
||||
std::string("已提交编辑: ") +
|
||||
DescribeSelectedComponent(result.changedComponentIndex) +
|
||||
" = " + result.committedText;
|
||||
return;
|
||||
@@ -657,7 +642,7 @@ private:
|
||||
}
|
||||
if (result.stepApplied || result.valueChanged) {
|
||||
m_lastResult =
|
||||
std::string("数值已更新,当前 component = ") +
|
||||
std::string("值已更新,当前 component = ") +
|
||||
DescribeSelectedComponent(result.changedComponentIndex);
|
||||
return;
|
||||
}
|
||||
@@ -669,15 +654,15 @@ private:
|
||||
}
|
||||
if (result.focusChanged) {
|
||||
m_lastResult =
|
||||
std::string("焦点变化: ") +
|
||||
std::string("焦点状态: ") +
|
||||
(m_interactionState.vector2FieldState.focused ? "focused" : "lost");
|
||||
return;
|
||||
}
|
||||
if (result.consumed) {
|
||||
m_lastResult = "控件已消费输入";
|
||||
m_lastResult = "输入已处理,但没有额外状态变化";
|
||||
return;
|
||||
}
|
||||
m_lastResult = "等待交互";
|
||||
m_lastResult = "无变化";
|
||||
}
|
||||
|
||||
void ExecuteAction(ActionId action) {
|
||||
@@ -702,16 +687,16 @@ private:
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
RefreshFrame();
|
||||
|
||||
const UIEditorVector2FieldHitTarget currentHit =
|
||||
HitTestUIEditorVector2Field(m_frame.layout, m_mousePosition);
|
||||
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(m_theme);
|
||||
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector2FieldPalette(m_theme);
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
|
||||
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics();
|
||||
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector2FieldPalette();
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorVector2FieldBasic");
|
||||
@@ -722,36 +707,36 @@ private:
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试验证什么功能",
|
||||
"验证 UIEditorVector2Field 的双通道数值编辑契约,不涉及 PropertyGrid 或业务 Inspector。");
|
||||
"这个测试在验证什么功能?",
|
||||
"验证 UIEditorVector2Field 的选择切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格承载。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 点击 X / Y 对应的 value box,检查 selected component 是否切换,并且应进入编辑态。",
|
||||
"1. 点击 X / Y value box 可切换 selected component,并检查 hover 与高亮。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 获得 focus 后按 Tab,检查 selected component 在 X / Y 之间切换。",
|
||||
"2. 获得 focus 后,按 Tab 在 X / Y 之间切换 selected component。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. 非编辑态按 Up / Down / Home / End,检查当前 component 的 step / 边界行为。",
|
||||
"3. 不进入编辑时,Up / Down / Home / End 会对当前 component 做 step 或边界跳转。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
|
||||
"4. 按 Enter 开始编辑;直接输入字符也应开始编辑;Enter commit,Escape cancel。",
|
||||
"4. 按 Enter 开始编辑,输入后再按 Enter 提交,按 Escape 取消。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. 编辑态按 F6 模拟 FocusLost,应提交暂存文本并退出编辑态。",
|
||||
"5. 按 F6 模拟 FocusLost,检查未提交编辑是否按约定结束。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f),
|
||||
"6. 检查 Hover / Selected / Editing / Values / Result 是否同步更新。",
|
||||
"6. 观察 Hover / Selected / Editing / Values / Result 是否同步。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
@@ -826,19 +811,13 @@ private:
|
||||
captureSummary,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 262.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.previewRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"Vector2Field 预览",
|
||||
"这里只放一个 Unity 风格的双通道 Vector2 字段。");
|
||||
"这里只放一个固定样式的 Vector2 输入项。");
|
||||
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
|
||||
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
|
||||
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
|
||||
@@ -880,12 +859,10 @@ private:
|
||||
UIEditorVector2FieldSpec m_spec = {};
|
||||
UIEditorVector2FieldInteractionState m_interactionState = {};
|
||||
UIEditorVector2FieldInteractionFrame m_frame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_hoveredAction = ActionId::Reset;
|
||||
bool m_hasHoveredAction = false;
|
||||
std::string m_lastResult = "等待交互";
|
||||
std::string m_themeStatus = "fallback";
|
||||
std::string m_lastResult = "无变化";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Core/UIEditorVector3FieldInteraction.h>
|
||||
#include <XCEditor/Widgets/UIEditorVector3Field.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorVector3FieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorVector3Field.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -48,8 +48,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldInvalidComponentIndex;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldSpec;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorVector3FieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Vector3Field Basic";
|
||||
|
||||
@@ -85,11 +83,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
@@ -443,14 +436,6 @@ private:
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector3_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad =
|
||||
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
if (themeLoad.succeeded) {
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = "loaded";
|
||||
} else {
|
||||
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
return true;
|
||||
@@ -479,7 +464,7 @@ private:
|
||||
return BuildScenarioLayout(
|
||||
width,
|
||||
height,
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
@@ -509,7 +494,7 @@ private:
|
||||
}
|
||||
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics();
|
||||
m_frame = UpdateUIEditorVector3FieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -629,7 +614,7 @@ private:
|
||||
|
||||
UIEditorVector3FieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
|
||||
const ScenarioLayout layout = GetLayout();
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(m_theme);
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics();
|
||||
m_frame = UpdateUIEditorVector3FieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -641,12 +626,12 @@ private:
|
||||
|
||||
void UpdateResultText(const UIEditorVector3FieldInteractionResult& result) {
|
||||
if (result.editCommitRejected) {
|
||||
m_lastResult = "提交失败,当前文本不是合法数字";
|
||||
m_lastResult = "提交被拒绝:当前输入不是合法数字";
|
||||
return;
|
||||
}
|
||||
if (result.editCommitted) {
|
||||
m_lastResult =
|
||||
std::string("已提交 ") +
|
||||
std::string("已提交编辑: ") +
|
||||
DescribeSelectedComponent(result.changedComponentIndex) +
|
||||
" = " + result.committedText;
|
||||
return;
|
||||
@@ -663,7 +648,7 @@ private:
|
||||
}
|
||||
if (result.stepApplied || result.valueChanged) {
|
||||
m_lastResult =
|
||||
std::string("数值已更新,当前 component = ") +
|
||||
std::string("值已更新,当前 component = ") +
|
||||
DescribeSelectedComponent(result.changedComponentIndex);
|
||||
return;
|
||||
}
|
||||
@@ -675,15 +660,15 @@ private:
|
||||
}
|
||||
if (result.focusChanged) {
|
||||
m_lastResult =
|
||||
std::string("焦点变化: ") +
|
||||
std::string("焦点状态: ") +
|
||||
(m_interactionState.vector3FieldState.focused ? "focused" : "lost");
|
||||
return;
|
||||
}
|
||||
if (result.consumed) {
|
||||
m_lastResult = "控件已消费输入";
|
||||
m_lastResult = "输入已处理,但没有额外状态变化";
|
||||
return;
|
||||
}
|
||||
m_lastResult = "等待交互";
|
||||
m_lastResult = "无变化";
|
||||
}
|
||||
|
||||
void ExecuteAction(ActionId action) {
|
||||
@@ -708,16 +693,16 @@ private:
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
RefreshFrame();
|
||||
|
||||
const UIEditorVector3FieldHitTarget currentHit =
|
||||
HitTestUIEditorVector3Field(m_frame.layout, m_mousePosition);
|
||||
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(m_theme);
|
||||
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector3FieldPalette(m_theme);
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
|
||||
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics();
|
||||
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector3FieldPalette();
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorVector3FieldBasic");
|
||||
@@ -728,36 +713,36 @@ private:
|
||||
layout.introRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"这个测试验证什么功能",
|
||||
"验证 UIEditorVector3Field 的三通道数值编辑契约,不涉及 PropertyGrid 或业务 Inspector。");
|
||||
"这个测试在验证什么功能?",
|
||||
"验证 UIEditorVector3Field 的选择切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格承载。");
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
|
||||
"1. 点击 X / Y / Z 对应的 value box,检查 selected component 是否切换,并应进入编辑态。",
|
||||
"1. 点击 X / Y / Z value box 可切换 selected component,并检查 hover 与高亮。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 获得 focus 后按 Tab,检查 selected component 在 X / Y / Z 之间切换。",
|
||||
"2. 获得 focus 后,按 Tab 在 X / Y / Z 之间切换 selected component。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
|
||||
"3. 非编辑态按 Up / Down / Home / End,检查当前 component 的 step / 边界行为。",
|
||||
"3. 不进入编辑时,Up / Down / Home / End 会对当前 component 做 step 或边界跳转。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
|
||||
"4. 按 Enter 开始编辑;直接输入字符也应开始编辑;Enter commit,Escape cancel。",
|
||||
"4. 按 Enter 开始编辑,输入后再按 Enter 提交,按 Escape 取消。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. 编辑态按 F6 模拟 FocusLost,应提交暂存文本并退出编辑态。",
|
||||
"5. 按 F6 模拟 FocusLost,检查未提交编辑是否按约定结束。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f),
|
||||
"6. 检查 Hover / Selected / Editing / Values / Result 是否同步更新。",
|
||||
"6. 观察 Hover / Selected / Editing / Values / Result 是否同步。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
@@ -834,19 +819,13 @@ private:
|
||||
captureSummary,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 262.0f),
|
||||
"Theme: " + m_themeStatus,
|
||||
shellPalette.textWeak,
|
||||
shellMetrics.bodyFontSize);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
layout.previewRect,
|
||||
shellPalette,
|
||||
shellMetrics,
|
||||
"Vector3Field 预览",
|
||||
"这里只放一个 Unity 风格的三通道 Vector3 字段。");
|
||||
"这里只放一个固定样式的 Vector3 输入项。");
|
||||
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
|
||||
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
|
||||
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
|
||||
@@ -888,12 +867,10 @@ private:
|
||||
UIEditorVector3FieldSpec m_spec = {};
|
||||
UIEditorVector3FieldInteractionState m_interactionState = {};
|
||||
UIEditorVector3FieldInteractionFrame m_frame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
ActionId m_hoveredAction = ActionId::Reset;
|
||||
bool m_hasHoveredAction = false;
|
||||
std::string m_lastResult = "等待交互";
|
||||
std::string m_themeStatus = "fallback";
|
||||
std::string m_lastResult = "无变化";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef NOMINMAX
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Core/UIEditorVector4FieldInteraction.h>
|
||||
#include <XCEditor/Widgets/UIEditorVector4Field.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Fields/UIEditorVector4FieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorVector4Field.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -46,8 +46,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorVector4Field;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldSpec;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorVector4FieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Vector4Field Basic";
|
||||
|
||||
@@ -69,11 +67,6 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
std::int32_t MapVectorKey(UINT keyCode) {
|
||||
switch (keyCode) {
|
||||
case VK_LEFT: return static_cast<std::int32_t>(KeyCode::Left);
|
||||
@@ -304,9 +297,6 @@ private:
|
||||
UpdateWindow(m_hwnd);
|
||||
m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector4_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad = XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = themeLoad.succeeded ? "loaded" : (themeLoad.error.empty() ? "fallback" : themeLoad.error);
|
||||
ResetScenario();
|
||||
return true;
|
||||
}
|
||||
@@ -344,8 +334,8 @@ private:
|
||||
const auto layout = BuildScenarioLayout(
|
||||
static_cast<float>((std::max)(1L, clientRect.right - clientRect.left)),
|
||||
static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top)),
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics(m_theme);
|
||||
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics();
|
||||
m_frame = UpdateUIEditorVector4FieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
@@ -357,21 +347,21 @@ private:
|
||||
|
||||
void UpdateResultText(const UIEditorVector4FieldInteractionResult& result) {
|
||||
if (result.editCommitRejected) {
|
||||
m_lastResult = "提交失败,当前文本不是合法数字";
|
||||
m_lastResult = "提交被拒绝:当前输入不是合法数字";
|
||||
} else if (result.editCommitted) {
|
||||
m_lastResult = "已提交 " + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText;
|
||||
m_lastResult = "已提交编辑: " + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText;
|
||||
} else if (result.editCanceled) {
|
||||
m_lastResult = "已取消编辑";
|
||||
} else if (result.editStarted) {
|
||||
m_lastResult = "开始编辑 component " + DescribeSelectedComponent(result.selectedComponentIndex);
|
||||
} else if (result.stepApplied || result.valueChanged) {
|
||||
m_lastResult = "数值已更新,当前 component = " + DescribeSelectedComponent(result.changedComponentIndex);
|
||||
m_lastResult = "值已更新,当前 component = " + DescribeSelectedComponent(result.changedComponentIndex);
|
||||
} else if (result.selectionChanged) {
|
||||
m_lastResult = "已切换选中 component: " + DescribeSelectedComponent(result.selectedComponentIndex);
|
||||
} else if (result.focusChanged) {
|
||||
m_lastResult = std::string("焦点变化: ") + (m_interactionState.vector4FieldState.focused ? "focused" : "lost");
|
||||
m_lastResult = std::string("焦点状态: ") + (m_interactionState.vector4FieldState.focused ? "focused" : "lost");
|
||||
} else if (result.consumed) {
|
||||
m_lastResult = "控件已消费输入";
|
||||
m_lastResult = "输入已处理,但没有额外状态变化";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,14 +370,14 @@ private:
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const auto layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
PumpEvents({});
|
||||
|
||||
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics(m_theme);
|
||||
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector4FieldPalette(m_theme);
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
|
||||
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics();
|
||||
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector4FieldPalette();
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
|
||||
const auto currentHit = HitTestUIEditorVector4Field(m_frame.layout, m_mousePosition);
|
||||
|
||||
UIDrawData drawData = {};
|
||||
@@ -396,12 +386,12 @@ private:
|
||||
|
||||
drawList.AddFilledRect(layout.introRect, shellPalette.cardBackground, shellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(layout.introRect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 14.0f), "这个测试验证什么功能?", shellPalette.textPrimary, shellMetrics.titleFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 40.0f), "只验证 UIEditorVector4Field 的四通道编辑契约。", shellPalette.textMuted, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), "1. 点击 X/Y/Z/W 的 value box,检查 selected component 和 editing。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), "2. 按 Tab/Shift+Tab 切换 component;Up/Down/Home/End 检查 step 与边界。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), "3. Enter 开始编辑;直接输入字符也应开始编辑;Enter commit,Escape cancel。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), "4. F6 模拟 FocusLost;F12 截图;R 重置当前测试。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 14.0f), "这个测试在验证什么功能?", shellPalette.textPrimary, shellMetrics.titleFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 40.0f), "验证 UIEditorVector4Field 的四分量切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格展示。", shellPalette.textMuted, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), "1. 点击 X / Y / Z / W 的 value box,可切换 selected component。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), "2. 按 Tab / Shift+Tab 切换 component;Up / Down / Home / End 检查 step 与边界。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), "3. 按 Enter 开始编辑;直接输入字符也应开始编辑;Enter 提交,Escape 取消。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), "4. 按 F6 模拟 FocusLost,按 F12 截图,按 R 重置当前测试。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), "5. 重点检查 Hover / Selected / Editing / Values / Result 是否同步。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
|
||||
drawList.AddFilledRect(layout.stateRect, shellPalette.cardBackground, shellMetrics.cardRadius);
|
||||
@@ -414,13 +404,12 @@ private:
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 142.0f), "Values: X=" + FormatUIEditorVector4FieldComponentValue(m_spec, 0u) + " Y=" + FormatUIEditorVector4FieldComponentValue(m_spec, 1u) + " Z=" + FormatUIEditorVector4FieldComponentValue(m_spec, 2u) + " W=" + FormatUIEditorVector4FieldComponentValue(m_spec, 3u), shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 166.0f), "Display: X=" + m_interactionState.vector4FieldState.displayTexts[0] + " Y=" + m_interactionState.vector4FieldState.displayTexts[1] + " Z=" + m_interactionState.vector4FieldState.displayTexts[2] + " W=" + m_interactionState.vector4FieldState.displayTexts[3], shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 190.0f), "Result: " + m_lastResult, shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), "Capture: " + (m_autoScreenshot.GetLastCaptureSummary().empty() ? std::string("F12 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), shellPalette.textWeak, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), "Theme: " + m_themeStatus, shellPalette.textWeak, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), "截图: " + (m_autoScreenshot.GetLastCaptureSummary().empty() ? std::string("F12 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), shellPalette.textWeak, shellMetrics.bodyFontSize);
|
||||
|
||||
drawList.AddFilledRect(layout.previewRect, shellPalette.cardBackground, shellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(layout.previewRect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius);
|
||||
drawList.AddText(UIPoint(layout.previewRect.x + 16.0f, layout.previewRect.y + 14.0f), "Vector4Field 预览", shellPalette.textPrimary, shellMetrics.titleFontSize);
|
||||
drawList.AddText(UIPoint(layout.previewRect.x + 16.0f, layout.previewRect.y + 40.0f), "这里只放一个 Unity 风格的四通道字段。", shellPalette.textMuted, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.previewRect.x + 16.0f, layout.previewRect.y + 40.0f), "这里只放一个固定样式的 Vector4 输入项。", shellPalette.textMuted, shellMetrics.bodyFontSize);
|
||||
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
|
||||
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
|
||||
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
|
||||
@@ -443,10 +432,8 @@ private:
|
||||
UIEditorVector4FieldSpec m_spec = {};
|
||||
UIEditorVector4FieldInteractionState m_interactionState = {};
|
||||
UIEditorVector4FieldInteractionFrame m_frame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
std::string m_lastResult = "等待交互";
|
||||
std::string m_themeStatus = "fallback";
|
||||
std::string m_lastResult = "无变化";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorViewportShell.h>
|
||||
#include <XCEditor/Widgets/UIEditorViewportSlot.h>
|
||||
#include <XCEditor/Shell/UIEditorViewportShell.h>
|
||||
#include <XCEditor/Shell/UIEditorViewportSlot.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/InputModifierTracker.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
@@ -506,7 +506,7 @@ private:
|
||||
UIEditorViewportShellSpec BuildShellSpec() const {
|
||||
UIEditorViewportShellSpec spec = {};
|
||||
spec.chrome.title = "Scene View";
|
||||
spec.chrome.subtitle = "ViewportShell 基础层";
|
||||
spec.chrome.subtitle = "ViewportShell 基础壳层";
|
||||
spec.chrome.showTopBar = m_showTopBar;
|
||||
spec.chrome.showBottomBar = m_showBottomBar;
|
||||
spec.chrome.topBarHeight = 40.0f;
|
||||
@@ -565,25 +565,25 @@ private:
|
||||
m_buttons = {
|
||||
{
|
||||
ActionId::ToggleTopBar,
|
||||
std::string("TopBar: ") + (m_showTopBar ? "开" : "关"),
|
||||
std::string("TopBar: ") + (m_showTopBar ? "开启" : "关闭"),
|
||||
UIRect(left, top, widthAvailable, buttonHeight),
|
||||
m_showTopBar
|
||||
},
|
||||
{
|
||||
ActionId::ToggleBottomBar,
|
||||
std::string("BottomBar: ") + (m_showBottomBar ? "开" : "关"),
|
||||
std::string("BottomBar: ") + (m_showBottomBar ? "开启" : "关闭"),
|
||||
UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight),
|
||||
m_showBottomBar
|
||||
},
|
||||
{
|
||||
ActionId::ToggleTexture,
|
||||
std::string("Texture: ") + (m_textureEnabled ? "开" : "关"),
|
||||
std::string("Texture: ") + (m_textureEnabled ? "开启" : "关闭"),
|
||||
UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight),
|
||||
m_textureEnabled
|
||||
},
|
||||
{
|
||||
ActionId::Reset,
|
||||
"Reset",
|
||||
"重置",
|
||||
UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight),
|
||||
false
|
||||
},
|
||||
@@ -687,41 +687,41 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
m_introRect,
|
||||
"测试功能:ViewportShell 基础 contract",
|
||||
"只验证 Resolve + Update,不接 Scene/Game 业务面板。");
|
||||
"这个测试验证什么功能?",
|
||||
"只验证 Resolve + Update 的 ViewportShell contract,不接 Scene/Game 业务。");
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f),
|
||||
"重点检查:切换 TopBar / BottomBar 后,Request Size 与 Input Rect 要同步变化。",
|
||||
"1. 验证 TopBar / BottomBar 对 Request Size 和 Input Rect 的影响。",
|
||||
kTextMuted,
|
||||
11.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f),
|
||||
"重点检查:Hover / Focus / Capture 要和右侧 surface 边框状态一致。",
|
||||
"2. 验证 Hover / Focus / Capture 是否与 surface 输入桥同步。",
|
||||
kTextMuted,
|
||||
11.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f),
|
||||
"重点检查:Texture 与 Fallback 只影响 frame 分支,不应破坏 shell 输入边界。",
|
||||
"3. 验证 Texture / Fallback 分支切换后,frame 输出仍由 shell 承接。",
|
||||
kTextMuted,
|
||||
11.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f),
|
||||
"操作:hover Surface,click 获取 Focus,按住拖出再松开,检查 Capture。",
|
||||
"4. 验证 hover Surface 与 click Surface 后的 Focus / Capture 变化。",
|
||||
kTextMuted,
|
||||
11.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 154.0f),
|
||||
"操作:点击左侧 TopBar / BottomBar / Texture,观察左侧状态与右侧布局。",
|
||||
"5. 验证 TopBar / BottomBar / Texture 三个开关切换后的整体表现。",
|
||||
kTextMuted,
|
||||
11.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 176.0f),
|
||||
"操作:需要保留当前画面时点“截图”,或直接按 F12。",
|
||||
"6. 验证截图链路,支持按钮触发和 F12 手动截图。",
|
||||
kTextMuted,
|
||||
11.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 204.0f),
|
||||
"结果判定:左侧 Request / Input / HoverHit / Hover / Focus / Capture 必须一致。",
|
||||
"7. 左侧持续展示 Request / Input / HoverHit / Hover / Focus / Capture 摘要。",
|
||||
kTextWeak,
|
||||
11.0f);
|
||||
|
||||
@@ -745,18 +745,18 @@ private:
|
||||
addStateLine("Hover Hit: " + DescribeHitTarget(m_hoverHit), kTextPrimary);
|
||||
addStateLine("Hover: " + BoolText(m_shellFrame.inputFrame.hovered), kTextPrimary);
|
||||
addStateLine("Focus: " + BoolText(m_shellFrame.inputFrame.focused), kTextPrimary);
|
||||
addStateLine("Capture: " + BoolText(m_shellFrame.inputFrame.captured), kTextPrimary);
|
||||
addStateLine("捕获: " + BoolText(m_shellFrame.inputFrame.captured), kTextPrimary);
|
||||
addStateLine("Result: " + m_lastResult, kTextMuted);
|
||||
|
||||
const std::string captureSummary =
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
? "截图排队中..."
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("截图:F12 或按钮 -> viewport_shell_basic/captures/")
|
||||
? std::string("截图: F12 或按钮 -> viewport_shell_basic/captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary());
|
||||
addStateLine(captureSummary, kTextWeak, 11.0f);
|
||||
|
||||
DrawCard(drawList, m_previewRect, "Preview", "这里只有一个 ViewportShell,用来检验 Editor 基础层 compose。");
|
||||
DrawCard(drawList, m_previewRect, "Preview", "这里只有一个 ViewportShell,用来检查 Editor 基础壳层 compose。");
|
||||
drawList.AddFilledRect(
|
||||
UIRect(
|
||||
m_previewRect.x + 12.0f,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorViewportSlot.h>
|
||||
#include <XCEditor/Shell/UIEditorViewportSlot.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -322,7 +322,7 @@ private:
|
||||
UIEditorViewportSlotChrome BuildChrome() const {
|
||||
UIEditorViewportSlotChrome chrome = {};
|
||||
chrome.title = "Scene View";
|
||||
chrome.subtitle = "ViewportSlot 基础壳层";
|
||||
chrome.subtitle = "ViewportSlot 基础布局验证";
|
||||
chrome.showTopBar = m_showTopBar;
|
||||
chrome.showBottomBar = m_showBottomBar;
|
||||
chrome.topBarHeight = 40.0f;
|
||||
@@ -341,7 +341,7 @@ private:
|
||||
frame.statusText = "Fake viewport frame";
|
||||
} else {
|
||||
frame.hasTexture = false;
|
||||
frame.statusText = "当前无纹理,检查 fallback message 与 input 边界。";
|
||||
frame.statusText = "当前无纹理,检查 fallback message 与 input 区域。";
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
@@ -414,10 +414,10 @@ private:
|
||||
const float width = m_controlsRect.width - 32.0f;
|
||||
m_buttons = {
|
||||
{ ActionId::ToggleTopBar, "TopBar", UIRect(left, top, width, buttonHeight), m_showTopBar },
|
||||
{ ActionId::ToggleBottomBar, "状态条", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_showBottomBar },
|
||||
{ ActionId::ToggleBottomBar, "BottomBar", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_showBottomBar },
|
||||
{ ActionId::ToggleTexture, "Texture", UIRect(left, top + (buttonHeight + gap) * 2.0f, width, buttonHeight), m_textureEnabled },
|
||||
{ ActionId::ToggleSquareAspect, "方形比例", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), m_squareAspect },
|
||||
{ ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false },
|
||||
{ ActionId::Reset, "重置", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false },
|
||||
{ ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 5.0f, width, buttonHeight), false }
|
||||
};
|
||||
}
|
||||
@@ -480,7 +480,7 @@ private:
|
||||
m_slotState.focused = true;
|
||||
m_slotState.surfaceActive = true;
|
||||
m_slotState.inputCaptured = true;
|
||||
m_lastResult = "Surface 按下:focus + active + capture";
|
||||
m_lastResult = "Surface 按下,进入 focus + active + capture";
|
||||
}
|
||||
InvalidateScenario();
|
||||
}
|
||||
@@ -493,7 +493,7 @@ private:
|
||||
break;
|
||||
case ActionId::ToggleBottomBar:
|
||||
m_showBottomBar = !m_showBottomBar;
|
||||
m_lastResult = m_showBottomBar ? "状态条已打开" : "状态条已关闭";
|
||||
m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关闭";
|
||||
break;
|
||||
case ActionId::ToggleTexture:
|
||||
m_textureEnabled = !m_textureEnabled;
|
||||
@@ -537,11 +537,11 @@ private:
|
||||
switch (hit.kind) {
|
||||
case UIEditorViewportSlotHitTargetKind::ToolItem:
|
||||
m_slotState.activeToolIndex = hit.index;
|
||||
m_lastResult = "ToolItem 命中:" + std::to_string(hit.index);
|
||||
m_lastResult = "ToolItem 命中: " + std::to_string(hit.index);
|
||||
break;
|
||||
case UIEditorViewportSlotHitTargetKind::StatusSegment:
|
||||
m_slotState.statusBarState.activeIndex = hit.index;
|
||||
m_lastResult = "StatusSegment 命中:" + std::to_string(hit.index);
|
||||
m_lastResult = "StatusSegment 命中: " + std::to_string(hit.index);
|
||||
break;
|
||||
case UIEditorViewportSlotHitTargetKind::Surface:
|
||||
m_slotState.focused = true;
|
||||
@@ -584,40 +584,40 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
m_introRect,
|
||||
"测试功能:ViewportSlot 基础壳层",
|
||||
"只验证 Editor 基础层。");
|
||||
"这个测试验证什么功能?",
|
||||
"只验证 Editor ViewportSlot 的基础壳层。");
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f),
|
||||
"检查1:TopBar / Surface / 状态条布局。",
|
||||
"1. 验证 TopBar / Surface / BottomBar 的区域划分。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f),
|
||||
"检查2:hover / focus / active / capture。",
|
||||
"2. 验证 hover / focus / active / capture 状态。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f),
|
||||
"操作1:hover toolbar / surface / status bar。",
|
||||
"3. 验证 hover toolbar / surface / status bar。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f),
|
||||
"操作2:click surface;按住左键观察 capture。",
|
||||
"4. 验证点击 surface 后进入 capture。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 154.0f),
|
||||
"操作3:切换 TopBar / 状态条 / Texture / 比例;F12 或“截图”。",
|
||||
"5. 验证切换 TopBar / BottomBar / Texture / 方形比例四个开关。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f),
|
||||
"结果:chrome 开关后 surface 扩张;Texture/Fallback 正确。",
|
||||
"6. Chrome 固定,surface 在 Texture / Fallback 之间切换。",
|
||||
kTextWeak,
|
||||
12.0f);
|
||||
|
||||
DrawCard(drawList, m_controlsRect, "开关", "只保留和 ViewportSlot contract 直接相关的操作。");
|
||||
DrawCard(drawList, m_controlsRect, "操作", "只保留和 ViewportSlot contract 直接相关的操作。");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
DrawButton(drawList, button);
|
||||
}
|
||||
@@ -640,12 +640,12 @@ private:
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 144.0f),
|
||||
"Capture: " + BoolText(m_slotState.inputCaptured),
|
||||
"捕获: " + BoolText(m_slotState.inputCaptured),
|
||||
kTextPrimary,
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 170.0f),
|
||||
"Chrome: TopBar " + BoolText(m_showTopBar) + " | 状态条 " + BoolText(m_showBottomBar),
|
||||
"Chrome: TopBar " + BoolText(m_showTopBar) + " | BottomBar " + BoolText(m_showBottomBar),
|
||||
kTextPrimary,
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
@@ -662,7 +662,7 @@ private:
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
? "截图排队中..."
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("截图:F12 或按钮 -> viewport_slot_basic/captures/")
|
||||
? std::string("截图: F12 或按钮 -> viewport_slot_basic/captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary());
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 248.0f),
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorWorkspaceInteraction.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceInteraction.h>
|
||||
#include "../../shared/src/EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -12,6 +14,8 @@
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -41,14 +45,20 @@ using XCEngine::UI::Editor::CollectUIEditorWorkspaceVisiblePanels;
|
||||
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame;
|
||||
using XCEngine::UI::Editor::Host::AutoScreenshotController;
|
||||
using XCEngine::UI::Editor::Host::NativeRenderer;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorDockHostMetrics;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorDockHostPalette;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorViewportSlotMetrics;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorViewportSlotPalette;
|
||||
using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
|
||||
using XCEngine::UI::Editor::UIEditorPanelRegistry;
|
||||
using XCEngine::UI::Editor::UIEditorViewportInputBridgeFrame;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceController;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceInteractionFrame;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceInteractionModel;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceInteractionResult;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceInteractionState;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceLayoutOperationStatus;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceModel;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
|
||||
@@ -56,18 +66,12 @@ using XCEngine::UI::Editor::UpdateUIEditorWorkspaceInteraction;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTargetKind;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorWorkspaceInteractionBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Workspace Interaction";
|
||||
const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
|
||||
const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorWorkspaceInteractionBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Workspace Interaction Basic";
|
||||
|
||||
constexpr UIColor kWindowBg(0.11f, 0.11f, 0.11f, 1.0f);
|
||||
constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f);
|
||||
constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f);
|
||||
constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f);
|
||||
constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f);
|
||||
constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f);
|
||||
constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
constexpr UIColor kButtonHover(0.32f, 0.32f, 0.32f, 1.0f);
|
||||
constexpr UIColor kButtonBorder(0.47f, 0.47f, 0.47f, 1.0f);
|
||||
constexpr UIColor kSuccess(0.48f, 0.72f, 0.52f, 1.0f);
|
||||
constexpr UIColor kWarning(0.82f, 0.67f, 0.35f, 1.0f);
|
||||
|
||||
@@ -91,6 +95,23 @@ std::filesystem::path ResolveRepoRootPath() {
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
bool IsAutoCaptureOnStartupEnabled() {
|
||||
const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP");
|
||||
if (value == nullptr || value[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string normalized = value;
|
||||
for (char& character : normalized) {
|
||||
character = static_cast<char>(std::tolower(static_cast<unsigned char>(character)));
|
||||
}
|
||||
|
||||
return normalized != "0" &&
|
||||
normalized != "false" &&
|
||||
normalized != "off" &&
|
||||
normalized != "no";
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UIRect& rect, float x, float y) {
|
||||
return x >= rect.x &&
|
||||
x <= rect.x + rect.width &&
|
||||
@@ -112,14 +133,22 @@ std::string FormatFloat(float value, int precision = 2) {
|
||||
|
||||
std::string DescribeHitTarget(const UIEditorDockHostHitTarget& target) {
|
||||
switch (target.kind) {
|
||||
case UIEditorDockHostHitTargetKind::SplitterHandle: return "Splitter: " + target.nodeId;
|
||||
case UIEditorDockHostHitTargetKind::TabStripBackground: return "TabStripBackground: " + target.nodeId;
|
||||
case UIEditorDockHostHitTargetKind::Tab: return "Tab: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::TabCloseButton: return "TabClose: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelHeader: return "PanelHeader: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelBody: return "PanelBody: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelFooter: return "PanelFooter: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelCloseButton: return "PanelClose: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::SplitterHandle:
|
||||
return "分割条: " + target.nodeId;
|
||||
case UIEditorDockHostHitTargetKind::TabStripBackground:
|
||||
return "标签栏背景: " + target.nodeId;
|
||||
case UIEditorDockHostHitTargetKind::Tab:
|
||||
return "标签: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::TabCloseButton:
|
||||
return "标签关闭: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelHeader:
|
||||
return "面板标题: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelBody:
|
||||
return "面板内容: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelFooter:
|
||||
return "面板底栏: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelCloseButton:
|
||||
return "面板关闭: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::None:
|
||||
default:
|
||||
return "None";
|
||||
@@ -146,52 +175,85 @@ std::string JoinVisiblePanelIds(const UIEditorWorkspaceController& controller) {
|
||||
|
||||
std::string DescribeViewportEvent(const UIEditorViewportInputBridgeFrame& frame) {
|
||||
if (frame.captureStarted) {
|
||||
return "Viewport CaptureStarted";
|
||||
return "视口开始捕获";
|
||||
}
|
||||
if (frame.captureEnded) {
|
||||
return "Viewport CaptureEnded";
|
||||
return "视口结束捕获";
|
||||
}
|
||||
if (frame.focusGained) {
|
||||
return "Viewport FocusGained";
|
||||
return "视口获得焦点";
|
||||
}
|
||||
if (frame.focusLost) {
|
||||
return "Viewport FocusLost";
|
||||
return "视口失去焦点";
|
||||
}
|
||||
if (frame.pointerPressedInside) {
|
||||
return "Viewport PointerDownInside";
|
||||
return "视口内按下";
|
||||
}
|
||||
if (frame.pointerReleasedInside) {
|
||||
return "Viewport PointerUpInside";
|
||||
return "视口内抬起";
|
||||
}
|
||||
if (frame.pointerMoved) {
|
||||
return "Viewport PointerMove";
|
||||
return "视口指针移动";
|
||||
}
|
||||
if (frame.wheelDelta != 0.0f) {
|
||||
return "Viewport Wheel " + FormatFloat(frame.wheelDelta);
|
||||
return "视口滚轮 " + FormatFloat(frame.wheelDelta);
|
||||
}
|
||||
return "Viewport Input";
|
||||
return "视口输入";
|
||||
}
|
||||
|
||||
void DrawCard(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
std::string_view title,
|
||||
std::string_view subtitle = {}) {
|
||||
drawList.AddFilledRect(rect, kCardBg, 10.0f);
|
||||
drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f);
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f);
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f);
|
||||
std::string DescribeValidationHitTarget(const UIEditorDockHostHitTarget& target) {
|
||||
switch (target.kind) {
|
||||
case UIEditorDockHostHitTargetKind::SplitterHandle:
|
||||
return "Splitter: " + target.nodeId;
|
||||
case UIEditorDockHostHitTargetKind::TabStripBackground:
|
||||
return "Tab strip background: " + target.nodeId;
|
||||
case UIEditorDockHostHitTargetKind::Tab:
|
||||
return "Tab: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::TabCloseButton:
|
||||
return "Tab close: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelHeader:
|
||||
return "Panel header: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelBody:
|
||||
return "Panel body: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelFooter:
|
||||
return "Panel footer: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::PanelCloseButton:
|
||||
return "Panel close: " + target.panelId;
|
||||
case UIEditorDockHostHitTargetKind::None:
|
||||
default:
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
|
||||
void DrawButton(UIDrawList& drawList, const ButtonState& button) {
|
||||
drawList.AddFilledRect(button.rect, button.hovered ? kButtonHover : kButtonBg, 8.0f);
|
||||
drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f);
|
||||
drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f);
|
||||
std::string DescribeValidationViewportEvent(const UIEditorViewportInputBridgeFrame& frame) {
|
||||
if (frame.captureStarted) {
|
||||
return "Viewport capture started";
|
||||
}
|
||||
if (frame.captureEnded) {
|
||||
return "Viewport capture ended";
|
||||
}
|
||||
if (frame.focusGained) {
|
||||
return "Viewport focus gained";
|
||||
}
|
||||
if (frame.focusLost) {
|
||||
return "Viewport focus lost";
|
||||
}
|
||||
if (frame.pointerPressedInside) {
|
||||
return "Viewport pointer down";
|
||||
}
|
||||
if (frame.pointerReleasedInside) {
|
||||
return "Viewport pointer up";
|
||||
}
|
||||
if (frame.pointerMoved) {
|
||||
return "Viewport pointer move";
|
||||
}
|
||||
if (frame.wheelDelta != 0.0f) {
|
||||
return "Viewport wheel " + FormatFloat(frame.wheelDelta);
|
||||
}
|
||||
return "Viewport input";
|
||||
}
|
||||
|
||||
UIEditorPanelRegistry BuildPanelRegistry() {
|
||||
UIEditorPanelRegistry BuildValidationPanelRegistry() {
|
||||
UIEditorPanelRegistry registry = {};
|
||||
registry.panels = {
|
||||
{ "viewport", "Viewport", UIEditorPanelPresentationKind::ViewportShell, false, true, true },
|
||||
@@ -201,7 +263,7 @@ UIEditorPanelRegistry BuildPanelRegistry() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceModel BuildWorkspace() {
|
||||
UIEditorWorkspaceModel BuildValidationWorkspace() {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceSplit(
|
||||
"root-split",
|
||||
@@ -219,17 +281,99 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
return workspace;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceInteractionModel BuildInteractionModel() {
|
||||
UIEditorWorkspaceInteractionModel BuildValidationInteractionModel() {
|
||||
UIEditorWorkspaceInteractionModel model = {};
|
||||
UIEditorWorkspacePanelPresentationModel presentation = {};
|
||||
presentation.panelId = "viewport";
|
||||
presentation.kind = UIEditorPanelPresentationKind::ViewportShell;
|
||||
presentation.viewportShellModel.spec.chrome.title = "Viewport";
|
||||
presentation.viewportShellModel.spec.chrome.subtitle = "Workspace Interaction";
|
||||
presentation.viewportShellModel.spec.chrome.subtitle = "Workspace interaction contract";
|
||||
presentation.viewportShellModel.spec.chrome.showTopBar = true;
|
||||
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
|
||||
presentation.viewportShellModel.frame.statusText =
|
||||
"这里只验证 WorkspaceInteraction contract,不接 Scene/Game 业务。";
|
||||
"Check splitter drag, tab switch, panel activation, and viewport input.";
|
||||
model.workspacePresentations = { presentation };
|
||||
return model;
|
||||
}
|
||||
|
||||
void DrawCard(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
std::string_view title,
|
||||
std::string_view subtitle = {}) {
|
||||
drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius);
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + 16.0f, rect.y + 14.0f),
|
||||
std::string(title),
|
||||
kShellPalette.textPrimary,
|
||||
kShellMetrics.titleFontSize);
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + 16.0f, rect.y + 38.0f),
|
||||
std::string(subtitle),
|
||||
kShellPalette.textMuted,
|
||||
kShellMetrics.bodyFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawButton(UIDrawList& drawList, const ButtonState& button) {
|
||||
drawList.AddFilledRect(
|
||||
button.rect,
|
||||
button.hovered ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground,
|
||||
kShellMetrics.buttonRadius);
|
||||
drawList.AddRectOutline(
|
||||
button.rect,
|
||||
kShellPalette.cardBorder,
|
||||
1.0f,
|
||||
kShellMetrics.buttonRadius);
|
||||
drawList.AddText(
|
||||
UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f),
|
||||
button.label,
|
||||
kShellPalette.textPrimary,
|
||||
kShellMetrics.bodyFontSize);
|
||||
}
|
||||
|
||||
UIEditorPanelRegistry BuildPanelRegistry() {
|
||||
UIEditorPanelRegistry registry = {};
|
||||
registry.panels = {
|
||||
{ "viewport", "视口", UIEditorPanelPresentationKind::ViewportShell, false, true, true },
|
||||
{ "doc", "文档", UIEditorPanelPresentationKind::Placeholder, true, true, true },
|
||||
{ "details", "详情", UIEditorPanelPresentationKind::Placeholder, true, true, true }
|
||||
};
|
||||
return registry;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceModel BuildWorkspace() {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceSplit(
|
||||
"root-split",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.7f,
|
||||
BuildUIEditorWorkspaceTabStack(
|
||||
"tab-stack",
|
||||
{
|
||||
BuildUIEditorWorkspacePanel("viewport-node", "viewport", "视口"),
|
||||
BuildUIEditorWorkspacePanel("doc-node", "doc", "文档", true)
|
||||
},
|
||||
0u),
|
||||
BuildUIEditorWorkspacePanel("details-node", "details", "详情", true));
|
||||
workspace.activePanelId = "viewport";
|
||||
return workspace;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceInteractionModel BuildInteractionModel() {
|
||||
UIEditorWorkspaceInteractionModel model = {};
|
||||
UIEditorWorkspacePanelPresentationModel presentation = {};
|
||||
presentation.panelId = "viewport";
|
||||
presentation.kind = UIEditorPanelPresentationKind::ViewportShell;
|
||||
presentation.viewportShellModel.spec.chrome.title = "视口";
|
||||
presentation.viewportShellModel.spec.chrome.subtitle =
|
||||
"验证 Workspace 与 Viewport 的统一交互";
|
||||
presentation.viewportShellModel.spec.chrome.showTopBar = true;
|
||||
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
|
||||
presentation.viewportShellModel.frame.statusText =
|
||||
"验证分割、标签切换、面板激活与视口输入桥接";
|
||||
model.workspacePresentations = { presentation };
|
||||
return model;
|
||||
}
|
||||
@@ -271,7 +415,9 @@ private:
|
||||
switch (message) {
|
||||
case WM_SIZE:
|
||||
if (app != nullptr && wParam != SIZE_MINIMIZED) {
|
||||
app->m_renderer.Resize(static_cast<UINT>(LOWORD(lParam)), static_cast<UINT>(HIWORD(lParam)));
|
||||
app->m_renderer.Resize(
|
||||
static_cast<UINT>(LOWORD(lParam)),
|
||||
static_cast<UINT>(HIWORD(lParam)));
|
||||
}
|
||||
return 0;
|
||||
case WM_PAINT:
|
||||
@@ -373,7 +519,8 @@ private:
|
||||
|
||||
bool Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
m_captureRoot =
|
||||
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/workspace_interaction_basic/captures";
|
||||
ResolveRepoRootPath() /
|
||||
"tests/UI/Editor/integration/shell/workspace_interaction_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
|
||||
WNDCLASSEXW windowClass = {};
|
||||
@@ -411,6 +558,11 @@ private:
|
||||
}
|
||||
|
||||
ResetScenario();
|
||||
if (IsAutoCaptureOnStartupEnabled()) {
|
||||
m_lastStatus = "Capture";
|
||||
m_lastMessage = "Startup capture queued. Verify the first frame before interacting.";
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -432,13 +584,15 @@ private:
|
||||
if (GetCapture() == m_hwnd) {
|
||||
ReleaseCapture();
|
||||
}
|
||||
m_controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
|
||||
m_controller = BuildDefaultUIEditorWorkspaceController(
|
||||
BuildValidationPanelRegistry(),
|
||||
BuildValidationWorkspace());
|
||||
m_interactionState = {};
|
||||
m_interactionModel = BuildInteractionModel();
|
||||
m_interactionModel = BuildValidationInteractionModel();
|
||||
m_cachedFrame = {};
|
||||
m_pendingInputEvents.clear();
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage = "等待交互。这里只验证 WorkspaceInteraction 统一输入路由 contract。";
|
||||
m_lastMessage = "Use the workspace on the right to exercise splitter drag, tab switch, and viewport input. Set XCUI_AUTO_CAPTURE_ON_STARTUP=1 to capture the startup frame.";
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
|
||||
@@ -468,8 +622,8 @@ private:
|
||||
const float left = m_controlsRect.x + 16.0f;
|
||||
const float top = m_controlsRect.y + 62.0f;
|
||||
m_buttons = {
|
||||
{ ActionId::Reset, "重置", UIRect(left, top, buttonWidth, 36.0f), false },
|
||||
{ ActionId::Capture, "截图(F12)", UIRect(left + buttonWidth + 12.0f, top, buttonWidth, 36.0f), false }
|
||||
{ ActionId::Reset, "Reset", UIRect(left, top, buttonWidth, 36.0f), false },
|
||||
{ ActionId::Capture, "Capture (F12)", UIRect(left + buttonWidth + 12.0f, top, buttonWidth, 36.0f), false }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -513,14 +667,14 @@ private:
|
||||
if (action == ActionId::Reset) {
|
||||
ResetScenario();
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage = "场景状态已重置。请重新检查 viewport body / tab switch / splitter drag。";
|
||||
m_lastMessage = "Scenario reset. Re-check splitter drag, tab switch, and viewport input.";
|
||||
m_lastColor = kWarning;
|
||||
return;
|
||||
}
|
||||
|
||||
m_autoScreenshot.RequestCapture("manual_button");
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage = "截图已排队,输出到 tests/UI/Editor/integration/shell/workspace_interaction_basic/captures/。";
|
||||
m_lastStatus = "Capture";
|
||||
m_lastMessage = "Capture queued. Check the Output/latest.png path shown below.";
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
|
||||
@@ -556,16 +710,16 @@ private:
|
||||
|
||||
void SetInteractionResult(const UIEditorWorkspaceInteractionResult& result) {
|
||||
if (result.dockHostResult.layoutResult.status !=
|
||||
XCEngine::UI::Editor::UIEditorWorkspaceLayoutOperationStatus::Rejected) {
|
||||
m_lastStatus = "DockHostLayout";
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected) {
|
||||
m_lastStatus = "Layout";
|
||||
m_lastMessage = result.dockHostResult.layoutResult.message;
|
||||
m_lastColor = kSuccess;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.dockHostResult.commandResult.status !=
|
||||
XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus::Rejected) {
|
||||
m_lastStatus = "DockHostCommand";
|
||||
UIEditorWorkspaceCommandStatus::Rejected) {
|
||||
m_lastStatus = "Command";
|
||||
m_lastMessage = result.dockHostResult.commandResult.message;
|
||||
m_lastColor = kSuccess;
|
||||
return;
|
||||
@@ -573,7 +727,7 @@ private:
|
||||
|
||||
if (!result.viewportPanelId.empty()) {
|
||||
m_lastStatus = result.viewportPanelId;
|
||||
m_lastMessage = DescribeViewportEvent(result.viewportInputFrame);
|
||||
m_lastMessage = DescribeValidationViewportEvent(result.viewportInputFrame);
|
||||
m_lastColor =
|
||||
result.viewportInputFrame.captureStarted || result.viewportInputFrame.focusGained
|
||||
? kSuccess
|
||||
@@ -583,33 +737,165 @@ private:
|
||||
|
||||
if (result.requestPointerCapture) {
|
||||
m_lastStatus = "Capture";
|
||||
m_lastMessage = "宿主已收到 WorkspaceInteraction 的 pointer capture 请求。";
|
||||
m_lastMessage = "Workspace interaction requested host pointer capture.";
|
||||
m_lastColor = kSuccess;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.releasePointerCapture) {
|
||||
m_lastStatus = "Release";
|
||||
m_lastMessage = "宿主已执行 WorkspaceInteraction 的 pointer release。";
|
||||
m_lastMessage = "Workspace interaction released host pointer capture.";
|
||||
m_lastColor = kWarning;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.consumed) {
|
||||
m_lastStatus = "Consumed";
|
||||
m_lastMessage = "这次输入被 WorkspaceInteraction 层消费。";
|
||||
m_lastMessage = "The workspace consumed the input, but no higher-level state changed.";
|
||||
m_lastColor = kWarning;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderValidationUI(float width, float height) {
|
||||
const auto* viewportFrame =
|
||||
FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.composeFrame, "viewport");
|
||||
const std::string selectedPresentation =
|
||||
viewportFrame != nullptr ? "ViewportShell" : "Placeholder";
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("WorkspaceInteractionBasic");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
m_introRect,
|
||||
"What This Validates",
|
||||
"Verify that workspace interaction and viewport input share one stable contract.");
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f),
|
||||
"1. Splitter drag should update the right-side layout ratio immediately.",
|
||||
kShellPalette.textPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f),
|
||||
"2. Clicking a tab, panel, or viewport should surface the hit target and result on the left.",
|
||||
kShellPalette.textPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f),
|
||||
"3. Viewport hover, focus, and capture changes should be reflected in the state card.",
|
||||
kShellPalette.textPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f),
|
||||
"4. This scene validates editor interaction plumbing only, not business-specific panels.",
|
||||
kShellPalette.textPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f),
|
||||
"Use XCUI_AUTO_CAPTURE_ON_STARTUP=1 for a startup shot; otherwise press F12 after exercising the scene.",
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
|
||||
DrawCard(drawList, m_controlsRect, "Actions", "Only reset and capture are exposed here.");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
DrawButton(drawList, button);
|
||||
}
|
||||
|
||||
DrawCard(drawList, m_stateRect, "State", "Observe workspace interaction and viewport input in real time.");
|
||||
float stateY = m_stateRect.y + 66.0f;
|
||||
auto addStateLine = [&](std::string text,
|
||||
const UIColor& color = kShellPalette.textPrimary,
|
||||
float fontSize = 12.0f) {
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize);
|
||||
stateY += 20.0f;
|
||||
};
|
||||
|
||||
addStateLine(
|
||||
"Hit target: " +
|
||||
DescribeValidationHitTarget(m_interactionState.dockHostInteractionState.dockHostState.hoveredTarget),
|
||||
kShellPalette.textPrimary,
|
||||
11.0f);
|
||||
addStateLine("Presentation: " + selectedPresentation, kShellPalette.textPrimary);
|
||||
addStateLine(
|
||||
"Active panel: " +
|
||||
(m_controller.GetWorkspace().activePanelId.empty()
|
||||
? std::string("(none)")
|
||||
: m_controller.GetWorkspace().activePanelId));
|
||||
addStateLine("Selected tab: " + GetSelectedTabId(), kShellPalette.textPrimary);
|
||||
addStateLine("Visible panels: " + JoinVisiblePanelIds(m_controller), kShellPalette.textWeak, 11.0f);
|
||||
addStateLine("Result: " + m_lastStatus, m_lastColor);
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f),
|
||||
m_lastMessage,
|
||||
kShellPalette.textMuted,
|
||||
11.0f);
|
||||
stateY += 34.0f;
|
||||
addStateLine(
|
||||
"Host capture: " + FormatBool(GetCapture() == m_hwnd),
|
||||
GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted);
|
||||
addStateLine(
|
||||
"Root split ratio: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
if (viewportFrame != nullptr) {
|
||||
addStateLine(
|
||||
"Viewport hover: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.hovered),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
addStateLine(
|
||||
"Viewport focus: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.focused),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
addStateLine(
|
||||
"Viewport capture: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.captured),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
}
|
||||
addStateLine(
|
||||
"Capture: " +
|
||||
(m_autoScreenshot.HasPendingCapture()
|
||||
? std::string("capture queued...")
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("Set XCUI_AUTO_CAPTURE_ON_STARTUP=1 or press F12.")
|
||||
: m_autoScreenshot.GetLastCaptureSummary())),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
|
||||
DrawCard(drawList, m_previewRect, "Preview", "The right side renders the real workspace interaction frame.");
|
||||
const auto dockPalette = ResolveUIEditorDockHostPalette();
|
||||
const auto viewportPalette = ResolveUIEditorViewportSlotPalette();
|
||||
const auto dockMetrics = ResolveUIEditorDockHostMetrics();
|
||||
const auto viewportMetrics = ResolveUIEditorViewportSlotMetrics();
|
||||
AppendUIEditorWorkspaceCompose(
|
||||
drawList,
|
||||
m_cachedFrame.composeFrame,
|
||||
dockPalette,
|
||||
dockMetrics,
|
||||
viewportPalette,
|
||||
viewportMetrics);
|
||||
|
||||
const bool framePresented = m_renderer.Render(drawData);
|
||||
m_autoScreenshot.CaptureIfRequested(
|
||||
m_renderer,
|
||||
drawData,
|
||||
static_cast<unsigned int>(width),
|
||||
static_cast<unsigned int>(height),
|
||||
framePresented);
|
||||
}
|
||||
|
||||
void RenderFrame() {
|
||||
UpdateLayout();
|
||||
const auto dockMetrics = ResolveUIEditorDockHostMetrics();
|
||||
const auto viewportMetrics = ResolveUIEditorViewportSlotMetrics();
|
||||
m_cachedFrame = UpdateUIEditorWorkspaceInteraction(
|
||||
m_interactionState,
|
||||
m_controller,
|
||||
m_workspaceRect,
|
||||
m_interactionModel,
|
||||
m_pendingInputEvents);
|
||||
m_pendingInputEvents,
|
||||
dockMetrics,
|
||||
viewportMetrics);
|
||||
m_pendingInputEvents.clear();
|
||||
ApplyHostCaptureRequests(m_cachedFrame.result);
|
||||
SetInteractionResult(m_cachedFrame.result);
|
||||
@@ -618,63 +904,126 @@ private:
|
||||
FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.composeFrame, "viewport");
|
||||
const bool viewportVisible = viewportFrame != nullptr;
|
||||
const std::string selectedPresentation =
|
||||
viewportVisible ? "ViewportShell" : "DockHost Placeholder";
|
||||
viewportVisible ? "视口呈现中" : "停用或未生成占位";
|
||||
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
|
||||
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
|
||||
|
||||
RenderValidationUI(width, height);
|
||||
return;
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("WorkspaceInteractionBasic");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground);
|
||||
|
||||
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 WorkspaceInteraction 统一路由 contract,不做 editor 业务。");
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), "1. 验证 DockHost splitter drag 与 ViewportShell input 可以被同一层统一收口。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), "2. 验证点击 viewport body 时,focus/capture 请求会直接冒泡给宿主。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), "3. 验证点击 Document tab 后,body 会立即从 ViewportShell 切回 DockHost placeholder。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 splitter 改尺寸后,viewport body bounds 与 workspace layout 同步变化。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作: 先点中间 viewport,再切到 Document,最后拖右侧 splitter。", kTextWeak, 11.0f);
|
||||
DrawCard(
|
||||
drawList,
|
||||
m_introRect,
|
||||
"这个测试验证什么",
|
||||
"验证 Workspace 基础交互与 Viewport 输入桥接是否统一。");
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f),
|
||||
"1. 拖拽分割条后,右侧工作区布局比例应实时变化。",
|
||||
kShellPalette.textPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f),
|
||||
"2. 点击标签、面板或视口时,左侧状态应显示命中目标与结果。",
|
||||
kShellPalette.textPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f),
|
||||
"3. 视口 hover、focus、capture 的变化应反馈到状态面板。",
|
||||
kShellPalette.textPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f),
|
||||
"4. 该场景只验证 Editor 基础交互,不验证具体业务面板。",
|
||||
kShellPalette.textPrimary,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f),
|
||||
"重点观察:命中目标、当前激活面板、宿主 capture 与视口输入状态。",
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
|
||||
DrawCard(drawList, m_controlsRect, "操作", "这里只保留 Reset / Capture。");
|
||||
DrawCard(drawList, m_controlsRect, "操作", "这里只保留重置和截图两个辅助动作。");
|
||||
for (const ButtonState& button : m_buttons) {
|
||||
DrawButton(drawList, button);
|
||||
}
|
||||
|
||||
DrawCard(drawList, m_stateRect, "状态", "重点检查 WorkspaceInteraction 当前状态。");
|
||||
DrawCard(drawList, m_stateRect, "状态", "观察当前工作区交互与视口输入状态。");
|
||||
float stateY = m_stateRect.y + 66.0f;
|
||||
auto addStateLine = [&](std::string text, const UIColor& color = kTextPrimary, float fontSize = 12.0f) {
|
||||
auto addStateLine = [&](std::string text,
|
||||
const UIColor& color = kShellPalette.textPrimary,
|
||||
float fontSize = 12.0f) {
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize);
|
||||
stateY += 20.0f;
|
||||
};
|
||||
|
||||
addStateLine("DockHost Hover: " + DescribeHitTarget(m_interactionState.dockHostInteractionState.dockHostState.hoveredTarget), kTextPrimary, 11.0f);
|
||||
addStateLine("Selected Presentation: " + selectedPresentation, kTextPrimary);
|
||||
addStateLine("Active Panel: " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId));
|
||||
addStateLine("Selected Tab: " + GetSelectedTabId(), kTextPrimary);
|
||||
addStateLine("Visible Panels: " + JoinVisiblePanelIds(m_controller), kTextWeak, 11.0f);
|
||||
addStateLine("Result: " + m_lastStatus, m_lastColor);
|
||||
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f);
|
||||
addStateLine(
|
||||
"命中目标: " +
|
||||
DescribeHitTarget(m_interactionState.dockHostInteractionState.dockHostState.hoveredTarget),
|
||||
kShellPalette.textPrimary,
|
||||
11.0f);
|
||||
addStateLine("当前呈现: " + selectedPresentation, kShellPalette.textPrimary);
|
||||
addStateLine(
|
||||
"当前激活: " +
|
||||
(m_controller.GetWorkspace().activePanelId.empty()
|
||||
? std::string("(none)")
|
||||
: m_controller.GetWorkspace().activePanelId));
|
||||
addStateLine("当前标签: " + GetSelectedTabId(), kShellPalette.textPrimary);
|
||||
addStateLine("可见面板: " + JoinVisiblePanelIds(m_controller), kShellPalette.textWeak, 11.0f);
|
||||
addStateLine("结果: " + m_lastStatus, m_lastColor);
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f),
|
||||
m_lastMessage,
|
||||
kShellPalette.textMuted,
|
||||
11.0f);
|
||||
stateY += 34.0f;
|
||||
addStateLine("Host Capture: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted);
|
||||
addStateLine("root-split ratio: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kTextWeak, 11.0f);
|
||||
addStateLine(
|
||||
"宿主 capture: " + FormatBool(GetCapture() == m_hwnd),
|
||||
GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted);
|
||||
addStateLine(
|
||||
"根分割比例: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
if (viewportFrame != nullptr) {
|
||||
addStateLine("Viewport Hover: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.hovered), kTextWeak, 11.0f);
|
||||
addStateLine("Viewport Focus: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.focused), kTextWeak, 11.0f);
|
||||
addStateLine("Viewport Capture: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.captured), kTextWeak, 11.0f);
|
||||
addStateLine(
|
||||
"视口 hover: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.hovered),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
addStateLine(
|
||||
"视口 focus: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.focused),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
addStateLine(
|
||||
"视口 capture: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.captured),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
}
|
||||
addStateLine(
|
||||
"截图: " +
|
||||
(m_autoScreenshot.HasPendingCapture()
|
||||
? std::string("截图排队中...")
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("F12 或 按钮 -> captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary())),
|
||||
kTextWeak,
|
||||
? std::string("截图排队中...")
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("按 F12 或按钮截图 -> captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary())),
|
||||
kShellPalette.textWeak,
|
||||
11.0f);
|
||||
|
||||
DrawCard(drawList, m_previewRect, "Preview", "真实 WorkspaceInteraction 预览,不再在 exe 宿主里手写拼装输入路由。");
|
||||
AppendUIEditorWorkspaceCompose(drawList, m_cachedFrame.composeFrame);
|
||||
DrawCard(drawList, m_previewRect, "预览", "右侧是真实的工作区交互预览。");
|
||||
const auto dockPalette = ResolveUIEditorDockHostPalette();
|
||||
const auto viewportPalette = ResolveUIEditorViewportSlotPalette();
|
||||
AppendUIEditorWorkspaceCompose(
|
||||
drawList,
|
||||
m_cachedFrame.composeFrame,
|
||||
dockPalette,
|
||||
dockMetrics,
|
||||
viewportPalette,
|
||||
viewportMetrics);
|
||||
|
||||
const bool framePresented = m_renderer.Render(drawData);
|
||||
m_autoScreenshot.CaptureIfRequested(
|
||||
@@ -704,7 +1053,7 @@ private:
|
||||
bool m_trackingMouseLeave = false;
|
||||
std::string m_lastStatus = {};
|
||||
std::string m_lastMessage = {};
|
||||
UIColor m_lastColor = kTextMuted;
|
||||
UIColor m_lastColor = kShellPalette.textMuted;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<View
|
||||
name="EditorWorkspaceShellComposeValidation"
|
||||
theme="../shared/themes/editor_validation.xctheme">
|
||||
name="EditorWorkspaceShellComposeValidation">
|
||||
<Column width="fill" height="fill" padding="20" gap="12">
|
||||
<Card
|
||||
title="测试内容:Editor Shell 基础壳层组合"
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorDockHostInteraction.h>
|
||||
#include <XCEditor/Core/UIEditorPanelRegistry.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Widgets/UIEditorDockHost.h>
|
||||
#include <XCEditor/Shell/UIEditorDockHostInteraction.h>
|
||||
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Shell/UIEditorDockHost.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -513,7 +513,7 @@ private:
|
||||
m_resetPressed = false;
|
||||
if (resetTriggered) {
|
||||
ResetScenario();
|
||||
m_lastResult = "Reset";
|
||||
m_lastResult = "已重置";
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
return;
|
||||
}
|
||||
@@ -555,22 +555,22 @@ private:
|
||||
}
|
||||
|
||||
if (result.requestPointerCapture) {
|
||||
m_lastResult = "Capture: begin splitter drag";
|
||||
m_lastResult = "捕获:开始拖动 splitter";
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.releasePointerCapture) {
|
||||
m_lastResult = "Capture: end splitter drag";
|
||||
m_lastResult = "捕获:结束拖动 splitter";
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.hitTarget.kind != UIEditorDockHostHitTargetKind::None) {
|
||||
m_lastResult = "Hover: " + DescribeHitTarget(result.hitTarget);
|
||||
m_lastResult = "悬停: " + DescribeHitTarget(result.hitTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.consumed) {
|
||||
m_lastResult = "Consumed: input handled by DockHostInteraction";
|
||||
m_lastResult = "已消费: 输入由 DockHostInteraction 处理";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,7 +597,7 @@ private:
|
||||
DrawCard(
|
||||
drawList,
|
||||
m_introRect,
|
||||
"这个测试在验证什么功能?",
|
||||
"这个测试验证什么功能?",
|
||||
"验证 Workspace + DockHost 的组合场景是否完全收口到统一交互层。");
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 68.0f),
|
||||
@@ -625,12 +625,12 @@ private:
|
||||
m_stateRect,
|
||||
"状态回显",
|
||||
"这里直接展示 hover / focus / dragging / active panel / split ratio。");
|
||||
DrawButton(drawList, m_resetButtonRect, "Reset", m_resetHovered);
|
||||
DrawButton(drawList, m_resetButtonRect, "重置", m_resetHovered);
|
||||
|
||||
const auto validation = m_controller.ValidateState();
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 70.0f),
|
||||
"Hover: " + DescribeHitTarget(m_interactionState.dockHostState.hoveredTarget),
|
||||
"悬停: " + DescribeHitTarget(m_interactionState.dockHostState.hoveredTarget),
|
||||
kTextPrimary,
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
@@ -640,7 +640,7 @@ private:
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 122.0f),
|
||||
"Capture: " + std::string(GetCapture() == m_hwnd ? "On" : "Off"),
|
||||
"捕获: " + std::string(GetCapture() == m_hwnd ? "开启" : "关闭"),
|
||||
GetCapture() == m_hwnd ? kSuccess : kTextMuted,
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
@@ -653,12 +653,12 @@ private:
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 174.0f),
|
||||
"Active Panel: " + m_controller.GetWorkspace().activePanelId,
|
||||
"当前激活面板: " + m_controller.GetWorkspace().activePanelId,
|
||||
kTextPrimary,
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 198.0f),
|
||||
"Visible Panels: " + JoinVisiblePanelIds(m_controller),
|
||||
"可见面板: " + JoinVisiblePanelIds(m_controller),
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
@@ -683,7 +683,7 @@ private:
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 382.0f),
|
||||
validation.IsValid() ? "Validation: OK" : "Validation: " + validation.message,
|
||||
validation.IsValid() ? "验证: OK" : "验证: " + validation.message,
|
||||
validation.IsValid() ? kSuccess : kDanger,
|
||||
12.0f);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user