From 6bf61ad8e2f7993a33d63eec90eb60061314e0b5 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 7 Apr 2026 13:01:48 +0800 Subject: [PATCH] Refine editor shell asset contract --- new_editor/app/Application.cpp | 19 ++- new_editor/src/Core/EditorShellAsset.cpp | 23 ++- .../shell/editor_shell_interaction/main.cpp | 139 ++++++++++++------ .../test_editor_shell_asset_validation.cpp | 3 +- 4 files changed, 130 insertions(+), 54 deletions(-) diff --git a/new_editor/app/Application.cpp b/new_editor/app/Application.cpp index 057d660f..d6646438 100644 --- a/new_editor/app/Application.cpp +++ b/new_editor/app/Application.cpp @@ -199,6 +199,9 @@ int Application::Run(HINSTANCE hInstance, int nCmdShow) { bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { m_hInstance = hInstance; m_shellAssetDefinition = BuildDefaultEditorShellAsset(ResolveRepoRootPath()); + m_structuredShell = BuildStructuredEditorShellBinding(m_shellAssetDefinition); + m_shellServices = BuildStructuredEditorShellServices(m_structuredShell); + m_screenAsset = m_structuredShell.screenAsset; WNDCLASSEXW windowClass = {}; windowClass.cbSize = sizeof(windowClass); @@ -397,14 +400,11 @@ void Application::QueueWindowFocusEvent(UIInputEventType type) { bool Application::LoadStructuredScreen(const char* triggerReason) { (void)triggerReason; - m_screenAsset = {}; - m_screenAsset.screenId = m_shellAssetDefinition.screenId; - m_screenAsset.documentPath = m_shellAssetDefinition.documentPath.string(); - m_screenAsset.themePath = m_shellAssetDefinition.themePath.string(); - + m_screenAsset = m_structuredShell.screenAsset; const bool loaded = m_screenPlayer.Load(m_screenAsset); - const EditorShellAssetValidationResult shellAssetValidation = - ValidateEditorShellAsset(m_shellAssetDefinition); + const EditorShellAssetValidationResult& shellAssetValidation = + m_structuredShell.assetValidation; + const auto shortcutValidation = m_structuredShell.shortcutManager.ValidateConfiguration(); m_useStructuredScreen = loaded; m_runtimeStatus = loaded ? "XCUI Editor Shell" : "Editor Shell | Load Error"; m_runtimeError.clear(); @@ -416,6 +416,11 @@ bool Application::LoadStructuredScreen(const char* triggerReason) { m_runtimeError, "Editor shell asset invalid: " + shellAssetValidation.message); } + if (!shortcutValidation.IsValid()) { + AppendErrorMessage( + m_runtimeError, + "Structured shell shortcut manager invalid: " + shortcutValidation.message); + } RebuildTrackedFileStates(); return loaded; } diff --git a/new_editor/src/Core/EditorShellAsset.cpp b/new_editor/src/Core/EditorShellAsset.cpp index 76fbc005..428f7639 100644 --- a/new_editor/src/Core/EditorShellAsset.cpp +++ b/new_editor/src/Core/EditorShellAsset.cpp @@ -19,6 +19,19 @@ Widgets::UIEditorStatusBarSegment BuildDefaultShellModeSegment() { return segment; } +Widgets::UIEditorStatusBarSegment BuildDefaultActivePanelSegment( + const UIEditorWorkspaceModel& workspace) { + Widgets::UIEditorStatusBarSegment segment = {}; + segment.segmentId = "active-panel"; + segment.label = workspace.activePanelId.empty() ? std::string("(none)") : workspace.activePanelId; + segment.slot = Widgets::UIEditorStatusBarSlot::Trailing; + segment.tone = Widgets::UIEditorStatusBarTextTone::Muted; + segment.interactive = false; + segment.showSeparator = false; + segment.desiredWidth = 144.0f; + return segment; +} + EditorShellAssetValidationResult MakeValidationError( EditorShellAssetValidationCode code, std::string message) { @@ -124,9 +137,13 @@ UIEditorWorkspacePanelPresentationModel BuildShellPresentation( } UIEditorShellInteractionDefinition BuildDefaultShellDefinition( - const UIEditorPanelRegistry& panelRegistry) { + const UIEditorPanelRegistry& panelRegistry, + const UIEditorWorkspaceModel& workspace) { UIEditorShellInteractionDefinition definition = {}; - definition.statusSegments = { BuildDefaultShellModeSegment() }; + definition.statusSegments = { + BuildDefaultShellModeSegment(), + BuildDefaultActivePanelSegment(workspace) + }; definition.workspacePresentations.reserve(panelRegistry.panels.size()); for (const UIEditorPanelDescriptor& descriptor : panelRegistry.panels) { definition.workspacePresentations.push_back(BuildShellPresentation(descriptor)); @@ -144,7 +161,7 @@ EditorShellAsset BuildDefaultEditorShellAsset(const std::filesystem::path& repoR asset.panelRegistry = BuildDefaultEditorShellPanelRegistry(); asset.workspace = BuildDefaultEditorShellWorkspaceModel(); asset.workspaceSession = BuildDefaultUIEditorWorkspaceSession(asset.panelRegistry, asset.workspace); - asset.shellDefinition = BuildDefaultShellDefinition(asset.panelRegistry); + asset.shellDefinition = BuildDefaultShellDefinition(asset.panelRegistry, asset.workspace); return asset; } diff --git a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp b/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp index e3902291..f9689e78 100644 --- a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp +++ b/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp @@ -2,6 +2,8 @@ #define NOMINMAX #endif +#include "Core/EditorShellAsset.h" + #include #include #include @@ -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 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); diff --git a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp index 9449573f..4c7e1a29 100644 --- a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp +++ b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp @@ -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");