Internalize editor shell asset definition contract

This commit is contained in:
2026-04-07 12:38:23 +08:00
parent d14fa6be07
commit 49e9b63a2d
5 changed files with 337 additions and 10 deletions

View File

@@ -10,6 +10,10 @@
#include "Core/EditorShellAsset.h"
#include <XCEditor/Core/UIEditorShellInteraction.h>
#include <XCEditor/Core/UIEditorShortcutManager.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEngine/UI/Runtime/UIScreenDocumentHost.h>
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
@@ -24,6 +28,46 @@
namespace XCEngine::UI::Editor {
struct StructuredEditorShellBinding {
::XCEngine::UI::Runtime::UIScreenAsset screenAsset = {};
UIEditorWorkspaceController workspaceController = {};
UIEditorShellInteractionDefinition shellDefinition = {};
UIEditorShortcutManager shortcutManager = {};
EditorShellAssetValidationResult assetValidation = {};
[[nodiscard]] bool IsValid() const {
return assetValidation.IsValid();
}
};
inline UIEditorShortcutManager BuildStructuredEditorShortcutManager(
const EditorShellAsset& asset) {
(void)asset;
return UIEditorShortcutManager(UIEditorCommandRegistry{});
}
inline StructuredEditorShellBinding BuildStructuredEditorShellBinding(
const EditorShellAsset& asset) {
StructuredEditorShellBinding binding = {};
binding.screenAsset.screenId = asset.screenId;
binding.screenAsset.documentPath = asset.documentPath.string();
binding.screenAsset.themePath = asset.themePath.string();
binding.workspaceController =
UIEditorWorkspaceController(asset.panelRegistry, asset.workspace, asset.workspaceSession);
binding.shellDefinition = asset.shellDefinition;
binding.shortcutManager = BuildStructuredEditorShortcutManager(asset);
binding.assetValidation = ValidateEditorShellAsset(asset);
return binding;
}
inline UIEditorShellInteractionServices BuildStructuredEditorShellServices(
const StructuredEditorShellBinding& binding) {
UIEditorShellInteractionServices services = {};
services.commandDispatcher = &binding.shortcutManager.GetCommandDispatcher();
services.shortcutManager = &binding.shortcutManager;
return services;
}
class Application {
public:
Application();
@@ -65,6 +109,8 @@ private:
::XCEngine::UI::Runtime::UIScreenPlayer m_screenPlayer;
::XCEngine::UI::Runtime::UIScreenAsset m_screenAsset = {};
EditorShellAsset m_shellAssetDefinition = {};
StructuredEditorShellBinding m_structuredShell = {};
UIEditorShellInteractionServices m_shellServices = {};
std::vector<TrackedFileState> m_trackedFiles = {};
std::chrono::steady_clock::time_point m_startTime = {};
std::chrono::steady_clock::time_point m_lastFrameTime = {};

View File

@@ -1,11 +1,24 @@
#include "EditorShellAsset.h"
#include <unordered_set>
#include <utility>
namespace XCEngine::UI::Editor {
namespace {
Widgets::UIEditorStatusBarSegment BuildDefaultShellModeSegment() {
Widgets::UIEditorStatusBarSegment segment = {};
segment.segmentId = "mode";
segment.label = "Editor Shell";
segment.slot = Widgets::UIEditorStatusBarSlot::Leading;
segment.tone = Widgets::UIEditorStatusBarTextTone::Primary;
segment.interactive = false;
segment.showSeparator = true;
segment.desiredWidth = 112.0f;
return segment;
}
EditorShellAssetValidationResult MakeValidationError(
EditorShellAssetValidationCode code,
std::string message) {
@@ -53,6 +66,74 @@ EditorShellAssetValidationResult ValidateWorkspacePanelsAgainstRegistry(
return {};
}
EditorShellAssetValidationResult ValidateShellDefinitionAgainstRegistry(
const UIEditorShellInteractionDefinition& definition,
const UIEditorPanelRegistry& panelRegistry) {
std::unordered_set<std::string> panelIds = {};
for (const UIEditorWorkspacePanelPresentationModel& presentation :
definition.workspacePresentations) {
if (!panelIds.insert(presentation.panelId).second) {
return MakeValidationError(
EditorShellAssetValidationCode::DuplicateShellPresentationPanelId,
"Shell definition presentation panel '" + presentation.panelId +
"' is duplicated.");
}
const UIEditorPanelDescriptor* descriptor =
FindUIEditorPanelDescriptor(panelRegistry, presentation.panelId);
if (descriptor == nullptr) {
return MakeValidationError(
EditorShellAssetValidationCode::MissingShellPresentationPanelDescriptor,
"Shell definition presentation panel '" + presentation.panelId +
"' is missing from the panel registry.");
}
if (presentation.kind != descriptor->presentationKind) {
return MakeValidationError(
EditorShellAssetValidationCode::ShellPresentationKindMismatch,
"Shell definition presentation panel '" + presentation.panelId +
"' kind does not match the panel registry.");
}
}
for (const UIEditorPanelDescriptor& descriptor : panelRegistry.panels) {
if (panelIds.find(descriptor.panelId) == panelIds.end()) {
return MakeValidationError(
EditorShellAssetValidationCode::MissingRequiredShellPresentation,
"Shell definition is missing presentation panel '" + descriptor.panelId +
"' required by the panel registry.");
}
}
return {};
}
UIEditorWorkspacePanelPresentationModel BuildShellPresentation(
const UIEditorPanelDescriptor& descriptor) {
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";
presentation.viewportShellModel.spec.chrome.showTopBar = true;
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
presentation.viewportShellModel.frame.statusText = descriptor.defaultTitle;
}
return presentation;
}
UIEditorShellInteractionDefinition BuildDefaultShellDefinition(
const UIEditorPanelRegistry& panelRegistry) {
UIEditorShellInteractionDefinition definition = {};
definition.statusSegments = { BuildDefaultShellModeSegment() };
definition.workspacePresentations.reserve(panelRegistry.panels.size());
for (const UIEditorPanelDescriptor& descriptor : panelRegistry.panels) {
definition.workspacePresentations.push_back(BuildShellPresentation(descriptor));
}
return definition;
}
} // namespace
EditorShellAsset BuildDefaultEditorShellAsset(const std::filesystem::path& repoRoot) {
@@ -63,6 +144,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);
return asset;
}
@@ -100,6 +182,12 @@ EditorShellAssetValidationResult ValidateEditorShellAsset(const EditorShellAsset
workspaceSessionValidation.message);
}
const EditorShellAssetValidationResult shellDefinitionValidation =
ValidateShellDefinitionAgainstRegistry(asset.shellDefinition, asset.panelRegistry);
if (!shellDefinitionValidation.IsValid()) {
return shellDefinitionValidation;
}
return {};
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <XCEditor/Core/UIEditorShellInteraction.h>
#include <XCEditor/Core/UIEditorPanelRegistry.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Core/UIEditorWorkspaceSession.h>
@@ -18,6 +19,7 @@ struct EditorShellAsset {
UIEditorPanelRegistry panelRegistry = {};
UIEditorWorkspaceModel workspace = {};
UIEditorWorkspaceSession workspaceSession = {};
UIEditorShellInteractionDefinition shellDefinition = {};
};
enum class EditorShellAssetValidationCode : std::uint8_t {
@@ -27,7 +29,11 @@ enum class EditorShellAssetValidationCode : std::uint8_t {
InvalidWorkspaceSession,
MissingPanelDescriptor,
PanelTitleMismatch,
PanelPlaceholderMismatch
PanelPlaceholderMismatch,
DuplicateShellPresentationPanelId,
MissingShellPresentationPanelDescriptor,
MissingRequiredShellPresentation,
ShellPresentationKindMismatch
};
struct EditorShellAssetValidationResult {