Refine editor shell asset contract

This commit is contained in:
2026-04-07 13:01:48 +08:00
parent e7669dc8c3
commit 6bf61ad8e2
4 changed files with 130 additions and 54 deletions

View File

@@ -2,6 +2,8 @@
#define NOMINMAX
#endif
#include "Core/EditorShellAsset.h"
#include <XCEditor/Core/UIEditorCommandDispatcher.h>
#include <XCEditor/Core/UIEditorMenuModel.h>
#include <XCEditor/Core/UIEditorShellInteraction.h>
@@ -40,11 +42,16 @@ using XCEngine::UI::UIPoint;
using XCEngine::UI::UIPointerButton;
using XCEngine::UI::UIRect;
using XCEngine::UI::Editor::AppendUIEditorShellInteraction;
using XCEngine::UI::Editor::BuildDefaultEditorShellAsset;
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceSession;
using XCEngine::UI::Editor::BuildEditorShellShortcutManager;
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController;
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
using XCEngine::UI::Editor::CollectUIEditorWorkspaceVisiblePanels;
using XCEngine::UI::Editor::EditorShellAsset;
using XCEngine::UI::Editor::EditorShellAssetValidationResult;
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame;
using XCEngine::UI::Editor::GetUIEditorCommandDispatchStatusName;
using XCEngine::UI::Editor::UpdateUIEditorShellInteraction;
@@ -64,6 +71,7 @@ using XCEngine::UI::Editor::UIEditorShellInteractionFrame;
using XCEngine::UI::Editor::UIEditorShellInteractionResult;
using XCEngine::UI::Editor::UIEditorShellInteractionServices;
using XCEngine::UI::Editor::UIEditorShellInteractionState;
using XCEngine::UI::Editor::UIEditorShortcutManager;
using XCEngine::UI::Editor::UIEditorViewportInputBridgeFrame;
using XCEngine::UI::Editor::UIEditorWorkspaceCommandKind;
using XCEngine::UI::Editor::UIEditorWorkspaceController;
@@ -71,6 +79,7 @@ using XCEngine::UI::Editor::UIEditorWorkspaceModel;
using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel;
using XCEngine::UI::Editor::UIEditorWorkspaceSession;
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
using XCEngine::UI::Editor::ValidateEditorShellAsset;
using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSegment;
using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSlot;
using XCEngine::UI::Widgets::UIPopupDismissReason;
@@ -380,6 +389,59 @@ UIEditorMenuModel BuildMenuModel() {
return model;
}
EditorShellAsset BuildScenarioShellAsset() {
EditorShellAsset asset = BuildDefaultEditorShellAsset(ResolveRepoRootPath());
asset.panelRegistry = BuildPanelRegistry();
asset.workspace = BuildWorkspace();
asset.workspaceSession =
BuildDefaultUIEditorWorkspaceSession(asset.panelRegistry, asset.workspace);
asset.shellDefinition = {};
asset.shellDefinition.menuModel = BuildMenuModel();
asset.shellDefinition.statusSegments = {
UIEditorStatusBarSegment{
"mode",
"Shell Contract",
UIEditorStatusBarSlot::Leading,
{},
true,
true,
122.0f
},
UIEditorStatusBarSegment{
"active",
asset.workspace.activePanelId.empty()
? std::string("(none)")
: asset.workspace.activePanelId,
UIEditorStatusBarSlot::Trailing,
{},
true,
true,
120.0f
}
};
asset.shellDefinition.workspacePresentations.reserve(asset.panelRegistry.panels.size());
for (const auto& descriptor : asset.panelRegistry.panels) {
UIEditorWorkspacePanelPresentationModel presentation = {};
presentation.panelId = descriptor.panelId;
presentation.kind = descriptor.presentationKind;
if (descriptor.presentationKind == UIEditorPanelPresentationKind::ViewportShell) {
presentation.viewportShellModel.spec.chrome.title = descriptor.defaultTitle;
presentation.viewportShellModel.spec.chrome.subtitle = "Editor Shell Interaction";
presentation.viewportShellModel.spec.chrome.showTopBar = true;
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
presentation.viewportShellModel.frame.hasTexture = false;
presentation.viewportShellModel.frame.statusText =
"这里只验证 Editor 根壳交互,不接旧 editor 业务面板。";
}
asset.shellDefinition.workspacePresentations.push_back(std::move(presentation));
}
asset.shortcutAsset.commandRegistry = BuildCommandRegistry();
return asset;
}
class ScenarioApp {
public:
int Run(HINSTANCE hInstance, int nCmdShow);
@@ -406,9 +468,11 @@ private:
NativeRenderer m_renderer = {};
AutoScreenshotController m_autoScreenshot = {};
std::filesystem::path m_captureRoot = {};
EditorShellAsset m_shellAsset = {};
EditorShellAssetValidationResult m_assetValidation = {};
UIEditorWorkspaceController m_controller = {};
UIEditorCommandDispatcher m_commandDispatcher = {};
UIEditorMenuModel m_menuModel = {};
UIEditorShortcutManager m_shortcutManager = {};
UIEditorShellInteractionServices m_shellServices = {};
UIEditorShellInteractionState m_interactionState = {};
UIEditorShellInteractionFrame m_cachedFrame = {};
std::vector<UIInputEvent> m_pendingInputEvents = {};
@@ -623,12 +687,26 @@ void ScenarioApp::ResetScenario() {
if (GetCapture() == m_hwnd) {
ReleaseCapture();
}
m_controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
m_commandDispatcher = UIEditorCommandDispatcher(BuildCommandRegistry());
m_menuModel = BuildMenuModel();
m_shellAsset = BuildScenarioShellAsset();
m_shellAsset.captureRootPath = m_captureRoot;
m_assetValidation = ValidateEditorShellAsset(m_shellAsset);
m_controller = UIEditorWorkspaceController(
m_shellAsset.panelRegistry,
m_shellAsset.workspace,
m_shellAsset.workspaceSession);
m_shortcutManager = BuildEditorShellShortcutManager(m_shellAsset);
m_shellServices = {};
m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher();
m_shellServices.shortcutManager = &m_shortcutManager;
m_interactionState = {};
m_cachedFrame = {};
m_pendingInputEvents.clear();
if (!m_assetValidation.IsValid()) {
m_lastStatus = "Invalid";
m_lastMessage = "场景 asset contract 非法: " + m_assetValidation.message;
m_lastColor = kDanger;
return;
}
m_lastStatus = "Ready";
m_lastMessage = "等待交互。这里只验证 Editor 根壳统一交互 contract不接旧 editor 业务。";
m_lastColor = kWarning;
@@ -732,42 +810,14 @@ void ScenarioApp::ApplyHostCaptureRequests(const UIEditorShellInteractionResult&
}
UIEditorShellInteractionDefinition ScenarioApp::BuildInteractionDefinition() const {
UIEditorShellInteractionDefinition definition = {};
definition.menuModel = m_menuModel;
definition.statusSegments = {
UIEditorStatusBarSegment{
"mode",
"Shell Contract",
UIEditorStatusBarSlot::Leading,
{},
true,
true,
122.0f
},
UIEditorStatusBarSegment{
"active",
m_controller.GetWorkspace().activePanelId.empty()
UIEditorShellInteractionDefinition definition = m_shellAsset.shellDefinition;
for (UIEditorStatusBarSegment& segment : definition.statusSegments) {
if (segment.segmentId == "active") {
segment.label = m_controller.GetWorkspace().activePanelId.empty()
? std::string("(none)")
: m_controller.GetWorkspace().activePanelId,
UIEditorStatusBarSlot::Trailing,
{},
true,
true,
120.0f
: m_controller.GetWorkspace().activePanelId;
}
};
UIEditorWorkspacePanelPresentationModel presentation = {};
presentation.panelId = "scene";
presentation.kind = UIEditorPanelPresentationKind::ViewportShell;
presentation.viewportShellModel.spec.chrome.title = "Scene";
presentation.viewportShellModel.spec.chrome.subtitle = "Editor Shell Interaction";
presentation.viewportShellModel.spec.chrome.showTopBar = true;
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
presentation.viewportShellModel.frame.hasTexture = false;
presentation.viewportShellModel.frame.statusText =
"这里只验证 Editor 根壳交互,不接旧 editor 业务面板。";
definition.workspacePresentations = { presentation };
}
return definition;
}
@@ -783,7 +833,7 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res
if (result.commandTriggered) {
m_lastStatus = "Triggered";
m_lastMessage = "命令已命中,但当前场景没有把 dispatcher 接到 root shell";
m_lastMessage = "命令已命中,但本帧没有完成 dispatch。请检查 shell services / asset contract";
m_lastColor = kWarning;
return;
}
@@ -866,15 +916,13 @@ void ScenarioApp::SetDispatchResult(const UIEditorCommandDispatchResult& result)
void ScenarioApp::RenderFrame() {
UpdateLayout();
const UIEditorShellInteractionDefinition definition = BuildInteractionDefinition();
UIEditorShellInteractionServices services = {};
services.commandDispatcher = &m_commandDispatcher;
m_cachedFrame = UpdateUIEditorShellInteraction(
m_interactionState,
m_controller,
m_shellRect,
definition,
m_pendingInputEvents,
services);
m_shellServices);
m_pendingInputEvents.clear();
ApplyHostCaptureRequests(m_cachedFrame.result);
SetInteractionResult(m_cachedFrame.result);
@@ -916,6 +964,11 @@ void ScenarioApp::RenderFrame() {
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(
"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);

View File

@@ -38,8 +38,9 @@ TEST(EditorShellAssetValidationTest, DefaultShellAssetPassesValidation) {
ASSERT_EQ(
shellAsset.shellDefinition.workspacePresentations.size(),
shellAsset.panelRegistry.panels.size());
ASSERT_EQ(shellAsset.shellDefinition.statusSegments.size(), 1u);
ASSERT_EQ(shellAsset.shellDefinition.statusSegments.size(), 2u);
EXPECT_EQ(shellAsset.shellDefinition.statusSegments.front().label, "Editor Shell");
EXPECT_EQ(shellAsset.shellDefinition.statusSegments.back().label, "editor-foundation-root");
EXPECT_EQ(
shellAsset.shellDefinition.workspacePresentations.front().panelId,
"editor-foundation-root");