Refine editor shell asset contract
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user