feat(xcui): advance core and editor validation flow

This commit is contained in:
2026-04-06 16:20:46 +08:00
parent 33bb84f650
commit 2d030a97da
128 changed files with 9961 additions and 773 deletions

View File

@@ -13,7 +13,10 @@ set(NEW_EDITOR_RESOURCE_FILES
add_library(XCNewEditorLib STATIC
src/editor/EditorShellAsset.cpp
src/editor/UIEditorPanelRegistry.cpp
src/editor/UIEditorWorkspaceController.cpp
src/editor/UIEditorWorkspaceModel.cpp
src/editor/UIEditorWorkspaceSession.cpp
src/Widgets/UIEditorCollectionPrimitives.cpp
)

View File

@@ -0,0 +1,52 @@
#pragma once
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::NewEditor {
enum class UIEditorPanelPresentationKind : std::uint8_t {
Placeholder = 0
};
struct UIEditorPanelDescriptor {
std::string panelId = {};
std::string defaultTitle = {};
UIEditorPanelPresentationKind presentationKind = UIEditorPanelPresentationKind::Placeholder;
bool placeholder = true;
bool canHide = true;
bool canClose = true;
};
struct UIEditorPanelRegistry {
std::vector<UIEditorPanelDescriptor> panels = {};
};
enum class UIEditorPanelRegistryValidationCode : std::uint8_t {
None = 0,
EmptyPanelId,
EmptyDefaultTitle,
DuplicatePanelId
};
struct UIEditorPanelRegistryValidationResult {
UIEditorPanelRegistryValidationCode code = UIEditorPanelRegistryValidationCode::None;
std::string message = {};
[[nodiscard]] bool IsValid() const {
return code == UIEditorPanelRegistryValidationCode::None;
}
};
UIEditorPanelRegistry BuildDefaultEditorShellPanelRegistry();
const UIEditorPanelDescriptor* FindUIEditorPanelDescriptor(
const UIEditorPanelRegistry& registry,
std::string_view panelId);
UIEditorPanelRegistryValidationResult ValidateUIEditorPanelRegistry(
const UIEditorPanelRegistry& registry);
} // namespace XCEngine::NewEditor

View File

@@ -0,0 +1,111 @@
#pragma once
#include <XCNewEditor/Editor/UIEditorWorkspaceSession.h>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::NewEditor {
enum class UIEditorWorkspaceCommandKind : std::uint8_t {
OpenPanel = 0,
ClosePanel,
ShowPanel,
HidePanel,
ActivatePanel,
ResetWorkspace
};
enum class UIEditorWorkspaceCommandStatus : std::uint8_t {
Changed = 0,
NoOp,
Rejected
};
struct UIEditorWorkspaceCommand {
UIEditorWorkspaceCommandKind kind = UIEditorWorkspaceCommandKind::ActivatePanel;
std::string panelId = {};
};
struct UIEditorWorkspaceCommandResult {
UIEditorWorkspaceCommandKind kind = UIEditorWorkspaceCommandKind::ActivatePanel;
UIEditorWorkspaceCommandStatus status = UIEditorWorkspaceCommandStatus::Rejected;
std::string panelId = {};
std::string message = {};
std::string activePanelId = {};
std::vector<std::string> visiblePanelIds = {};
};
enum class UIEditorWorkspaceControllerValidationCode : std::uint8_t {
None = 0,
InvalidPanelRegistry,
InvalidWorkspace,
InvalidWorkspaceSession
};
struct UIEditorWorkspaceControllerValidationResult {
UIEditorWorkspaceControllerValidationCode code =
UIEditorWorkspaceControllerValidationCode::None;
std::string message = {};
[[nodiscard]] bool IsValid() const {
return code == UIEditorWorkspaceControllerValidationCode::None;
}
};
std::string_view GetUIEditorWorkspaceCommandKindName(UIEditorWorkspaceCommandKind kind);
std::string_view GetUIEditorWorkspaceCommandStatusName(UIEditorWorkspaceCommandStatus status);
class UIEditorWorkspaceController {
public:
UIEditorWorkspaceController() = default;
UIEditorWorkspaceController(
UIEditorPanelRegistry panelRegistry,
UIEditorWorkspaceModel workspace,
UIEditorWorkspaceSession session);
const UIEditorPanelRegistry& GetPanelRegistry() const {
return m_panelRegistry;
}
const UIEditorWorkspaceModel& GetWorkspace() const {
return m_workspace;
}
const UIEditorWorkspaceSession& GetSession() const {
return m_session;
}
UIEditorWorkspaceControllerValidationResult ValidateState() const;
UIEditorWorkspaceCommandResult Dispatch(const UIEditorWorkspaceCommand& command);
private:
UIEditorWorkspaceCommandResult BuildResult(
const UIEditorWorkspaceCommand& command,
UIEditorWorkspaceCommandStatus status,
std::string message) const;
UIEditorWorkspaceCommandResult FinalizeMutation(
const UIEditorWorkspaceCommand& command,
bool changed,
std::string changedMessage,
std::string unexpectedFailureMessage,
const UIEditorWorkspaceModel& previousWorkspace,
const UIEditorWorkspaceSession& previousSession);
const UIEditorPanelDescriptor* FindPanelDescriptor(std::string_view panelId) const;
UIEditorPanelRegistry m_panelRegistry = {};
UIEditorWorkspaceModel m_baselineWorkspace = {};
UIEditorWorkspaceSession m_baselineSession = {};
UIEditorWorkspaceModel m_workspace = {};
UIEditorWorkspaceSession m_session = {};
};
UIEditorWorkspaceController BuildDefaultUIEditorWorkspaceController(
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace);
} // namespace XCEngine::NewEditor

View File

@@ -70,6 +70,8 @@ struct UIEditorWorkspaceVisiblePanel {
bool placeholder = false;
};
UIEditorWorkspaceModel BuildDefaultEditorShellWorkspaceModel();
UIEditorWorkspaceNode BuildUIEditorWorkspacePanel(
std::string nodeId,
std::string panelId,

View File

@@ -0,0 +1,94 @@
#pragma once
#include <XCNewEditor/Editor/UIEditorPanelRegistry.h>
#include <XCNewEditor/Editor/UIEditorWorkspaceModel.h>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine::NewEditor {
struct UIEditorPanelSessionState {
std::string panelId = {};
bool open = true;
bool visible = true;
};
struct UIEditorWorkspaceSession {
std::vector<UIEditorPanelSessionState> panelStates = {};
};
enum class UIEditorWorkspaceSessionValidationCode : std::uint8_t {
None = 0,
MissingPanelState,
UnknownPanelId,
DuplicatePanelId,
ClosedPanelVisible,
NonHideablePanelHidden,
NonCloseablePanelClosed,
InvalidActivePanelId
};
struct UIEditorWorkspaceSessionValidationResult {
UIEditorWorkspaceSessionValidationCode code = UIEditorWorkspaceSessionValidationCode::None;
std::string message = {};
[[nodiscard]] bool IsValid() const {
return code == UIEditorWorkspaceSessionValidationCode::None;
}
};
UIEditorWorkspaceSession BuildDefaultUIEditorWorkspaceSession(
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace);
const UIEditorPanelSessionState* FindUIEditorPanelSessionState(
const UIEditorWorkspaceSession& session,
std::string_view panelId);
UIEditorWorkspaceSessionValidationResult ValidateUIEditorWorkspaceSession(
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session);
std::vector<UIEditorWorkspaceVisiblePanel> CollectUIEditorWorkspaceVisiblePanels(
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session);
const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel(
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session);
bool TryOpenUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId);
bool TryCloseUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId);
bool TryShowUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId);
bool TryHideUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId);
bool TryActivateUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId);
} // namespace XCEngine::NewEditor

View File

@@ -82,6 +82,17 @@ std::string FormatPoint(const UIPoint& point) {
return "(" + FormatFloat(point.x) + ", " + FormatFloat(point.y) + ")";
}
void AppendErrorMessage(std::string& target, const std::string& message) {
if (message.empty()) {
return;
}
if (!target.empty()) {
target += " | ";
}
target += message;
}
std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) {
switch (wParam) {
case 'A': return static_cast<std::int32_t>(KeyCode::A);
@@ -392,9 +403,19 @@ bool Application::LoadStructuredScreen(const char* triggerReason) {
m_screenAsset.themePath = m_shellAssetDefinition.themePath.string();
const bool loaded = m_screenPlayer.Load(m_screenAsset);
const EditorShellAssetValidationResult shellAssetValidation =
ValidateEditorShellAsset(m_shellAssetDefinition);
m_useStructuredScreen = loaded;
m_runtimeStatus = loaded ? "XCUI Editor Shell" : "Editor Shell | Load Error";
m_runtimeError = loaded ? std::string() : m_screenPlayer.GetLastError();
m_runtimeError.clear();
if (!loaded) {
AppendErrorMessage(m_runtimeError, m_screenPlayer.GetLastError());
}
if (!shellAssetValidation.IsValid()) {
AppendErrorMessage(
m_runtimeError,
"Editor shell asset invalid: " + shellAssetValidation.message);
}
RebuildTrackedFileStates();
return loaded;
}

View File

@@ -1,13 +1,106 @@
#include "EditorShellAsset.h"
#include <utility>
namespace XCEngine::NewEditor {
namespace {
EditorShellAssetValidationResult MakeValidationError(
EditorShellAssetValidationCode code,
std::string message) {
EditorShellAssetValidationResult result = {};
result.code = code;
result.message = std::move(message);
return result;
}
EditorShellAssetValidationResult ValidateWorkspacePanelsAgainstRegistry(
const UIEditorWorkspaceNode& node,
const UIEditorPanelRegistry& panelRegistry) {
if (node.kind == UIEditorWorkspaceNodeKind::Panel) {
const UIEditorPanelDescriptor* descriptor =
FindUIEditorPanelDescriptor(panelRegistry, node.panel.panelId);
if (descriptor == nullptr) {
return MakeValidationError(
EditorShellAssetValidationCode::MissingPanelDescriptor,
"Workspace panel '" + node.panel.panelId + "' is missing from the panel registry.");
}
if (node.panel.title != descriptor->defaultTitle) {
return MakeValidationError(
EditorShellAssetValidationCode::PanelTitleMismatch,
"Workspace panel '" + node.panel.panelId + "' title does not match the registry default title.");
}
if (node.panel.placeholder != descriptor->placeholder) {
return MakeValidationError(
EditorShellAssetValidationCode::PanelPlaceholderMismatch,
"Workspace panel '" + node.panel.panelId + "' placeholder flag does not match the registry descriptor.");
}
return {};
}
for (const UIEditorWorkspaceNode& child : node.children) {
EditorShellAssetValidationResult result =
ValidateWorkspacePanelsAgainstRegistry(child, panelRegistry);
if (!result.IsValid()) {
return result;
}
}
return {};
}
} // namespace
EditorShellAsset BuildDefaultEditorShellAsset(const std::filesystem::path& repoRoot) {
EditorShellAsset asset = {};
asset.documentPath = (repoRoot / "new_editor/ui/views/editor_shell.xcui").lexically_normal();
asset.themePath = (repoRoot / "new_editor/ui/themes/editor_shell.xctheme").lexically_normal();
asset.captureRootPath = (repoRoot / "new_editor/captures").lexically_normal();
asset.panelRegistry = BuildDefaultEditorShellPanelRegistry();
asset.workspace = BuildDefaultEditorShellWorkspaceModel();
asset.workspaceSession = BuildDefaultUIEditorWorkspaceSession(asset.panelRegistry, asset.workspace);
return asset;
}
EditorShellAssetValidationResult ValidateEditorShellAsset(const EditorShellAsset& asset) {
const UIEditorPanelRegistryValidationResult registryValidation =
ValidateUIEditorPanelRegistry(asset.panelRegistry);
if (!registryValidation.IsValid()) {
return MakeValidationError(
EditorShellAssetValidationCode::InvalidPanelRegistry,
registryValidation.message);
}
const UIEditorWorkspaceValidationResult workspaceValidation =
ValidateUIEditorWorkspace(asset.workspace);
if (!workspaceValidation.IsValid()) {
return MakeValidationError(
EditorShellAssetValidationCode::InvalidWorkspace,
workspaceValidation.message);
}
const EditorShellAssetValidationResult panelRegistryConsistency =
ValidateWorkspacePanelsAgainstRegistry(asset.workspace.root, asset.panelRegistry);
if (!panelRegistryConsistency.IsValid()) {
return panelRegistryConsistency;
}
const UIEditorWorkspaceSessionValidationResult workspaceSessionValidation =
ValidateUIEditorWorkspaceSession(
asset.panelRegistry,
asset.workspace,
asset.workspaceSession);
if (!workspaceSessionValidation.IsValid()) {
return MakeValidationError(
EditorShellAssetValidationCode::InvalidWorkspaceSession,
workspaceSessionValidation.message);
}
return {};
}
} // namespace XCEngine::NewEditor

View File

@@ -1,5 +1,10 @@
#pragma once
#include <XCNewEditor/Editor/UIEditorPanelRegistry.h>
#include <XCNewEditor/Editor/UIEditorWorkspaceModel.h>
#include <XCNewEditor/Editor/UIEditorWorkspaceSession.h>
#include <cstdint>
#include <filesystem>
#include <string>
@@ -10,8 +15,31 @@ struct EditorShellAsset {
std::filesystem::path documentPath = {};
std::filesystem::path themePath = {};
std::filesystem::path captureRootPath = {};
UIEditorPanelRegistry panelRegistry = {};
UIEditorWorkspaceModel workspace = {};
UIEditorWorkspaceSession workspaceSession = {};
};
enum class EditorShellAssetValidationCode : std::uint8_t {
None = 0,
InvalidPanelRegistry,
InvalidWorkspace,
InvalidWorkspaceSession,
MissingPanelDescriptor,
PanelTitleMismatch,
PanelPlaceholderMismatch
};
struct EditorShellAssetValidationResult {
EditorShellAssetValidationCode code = EditorShellAssetValidationCode::None;
std::string message = {};
[[nodiscard]] bool IsValid() const {
return code == EditorShellAssetValidationCode::None;
}
};
EditorShellAsset BuildDefaultEditorShellAsset(const std::filesystem::path& repoRoot);
EditorShellAssetValidationResult ValidateEditorShellAsset(const EditorShellAsset& asset);
} // namespace XCEngine::NewEditor

View File

@@ -0,0 +1,74 @@
#include <XCNewEditor/Editor/UIEditorPanelRegistry.h>
#include <unordered_set>
#include <utility>
namespace XCEngine::NewEditor {
namespace {
UIEditorPanelRegistryValidationResult MakeValidationError(
UIEditorPanelRegistryValidationCode code,
std::string message) {
UIEditorPanelRegistryValidationResult result = {};
result.code = code;
result.message = std::move(message);
return result;
}
} // namespace
UIEditorPanelRegistry BuildDefaultEditorShellPanelRegistry() {
UIEditorPanelRegistry registry = {};
registry.panels = {
{
"editor-foundation-root",
"Root Surface",
UIEditorPanelPresentationKind::Placeholder,
true,
false,
false
}
};
return registry;
}
const UIEditorPanelDescriptor* FindUIEditorPanelDescriptor(
const UIEditorPanelRegistry& registry,
std::string_view panelId) {
for (const UIEditorPanelDescriptor& descriptor : registry.panels) {
if (descriptor.panelId == panelId) {
return &descriptor;
}
}
return nullptr;
}
UIEditorPanelRegistryValidationResult ValidateUIEditorPanelRegistry(
const UIEditorPanelRegistry& registry) {
std::unordered_set<std::string> panelIds = {};
for (const UIEditorPanelDescriptor& descriptor : registry.panels) {
if (descriptor.panelId.empty()) {
return MakeValidationError(
UIEditorPanelRegistryValidationCode::EmptyPanelId,
"Panel registry entry must define a panelId.");
}
if (descriptor.defaultTitle.empty()) {
return MakeValidationError(
UIEditorPanelRegistryValidationCode::EmptyDefaultTitle,
"Panel descriptor '" + descriptor.panelId + "' must define a defaultTitle.");
}
if (!panelIds.insert(descriptor.panelId).second) {
return MakeValidationError(
UIEditorPanelRegistryValidationCode::DuplicatePanelId,
"Panel descriptor '" + descriptor.panelId + "' is duplicated in the registry.");
}
}
return {};
}
} // namespace XCEngine::NewEditor

View File

@@ -0,0 +1,357 @@
#include <XCNewEditor/Editor/UIEditorWorkspaceController.h>
#include <utility>
namespace XCEngine::NewEditor {
namespace {
bool AreWorkspaceNodesEquivalent(
const UIEditorWorkspaceNode& lhs,
const UIEditorWorkspaceNode& rhs) {
if (lhs.kind != rhs.kind ||
lhs.nodeId != rhs.nodeId ||
lhs.splitAxis != rhs.splitAxis ||
lhs.splitRatio != rhs.splitRatio ||
lhs.selectedTabIndex != rhs.selectedTabIndex ||
lhs.panel.panelId != rhs.panel.panelId ||
lhs.panel.title != rhs.panel.title ||
lhs.panel.placeholder != rhs.panel.placeholder ||
lhs.children.size() != rhs.children.size()) {
return false;
}
for (std::size_t index = 0; index < lhs.children.size(); ++index) {
if (!AreWorkspaceNodesEquivalent(lhs.children[index], rhs.children[index])) {
return false;
}
}
return true;
}
bool AreWorkspaceModelsEquivalent(
const UIEditorWorkspaceModel& lhs,
const UIEditorWorkspaceModel& rhs) {
return lhs.activePanelId == rhs.activePanelId &&
AreWorkspaceNodesEquivalent(lhs.root, rhs.root);
}
bool AreWorkspaceSessionsEquivalent(
const UIEditorWorkspaceSession& lhs,
const UIEditorWorkspaceSession& rhs) {
if (lhs.panelStates.size() != rhs.panelStates.size()) {
return false;
}
for (std::size_t index = 0; index < lhs.panelStates.size(); ++index) {
const UIEditorPanelSessionState& lhsState = lhs.panelStates[index];
const UIEditorPanelSessionState& rhsState = rhs.panelStates[index];
if (lhsState.panelId != rhsState.panelId ||
lhsState.open != rhsState.open ||
lhsState.visible != rhsState.visible) {
return false;
}
}
return true;
}
std::vector<std::string> CollectVisiblePanelIds(
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session) {
const std::vector<UIEditorWorkspaceVisiblePanel> panels =
CollectUIEditorWorkspaceVisiblePanels(workspace, session);
std::vector<std::string> ids = {};
ids.reserve(panels.size());
for (const UIEditorWorkspaceVisiblePanel& panel : panels) {
ids.push_back(panel.panelId);
}
return ids;
}
} // namespace
std::string_view GetUIEditorWorkspaceCommandKindName(UIEditorWorkspaceCommandKind kind) {
switch (kind) {
case UIEditorWorkspaceCommandKind::OpenPanel:
return "OpenPanel";
case UIEditorWorkspaceCommandKind::ClosePanel:
return "ClosePanel";
case UIEditorWorkspaceCommandKind::ShowPanel:
return "ShowPanel";
case UIEditorWorkspaceCommandKind::HidePanel:
return "HidePanel";
case UIEditorWorkspaceCommandKind::ActivatePanel:
return "ActivatePanel";
case UIEditorWorkspaceCommandKind::ResetWorkspace:
return "ResetWorkspace";
}
return "Unknown";
}
std::string_view GetUIEditorWorkspaceCommandStatusName(UIEditorWorkspaceCommandStatus status) {
switch (status) {
case UIEditorWorkspaceCommandStatus::Changed:
return "Changed";
case UIEditorWorkspaceCommandStatus::NoOp:
return "NoOp";
case UIEditorWorkspaceCommandStatus::Rejected:
return "Rejected";
}
return "Unknown";
}
UIEditorWorkspaceController::UIEditorWorkspaceController(
UIEditorPanelRegistry panelRegistry,
UIEditorWorkspaceModel workspace,
UIEditorWorkspaceSession session)
: m_panelRegistry(std::move(panelRegistry))
, m_baselineWorkspace(workspace)
, m_baselineSession(session)
, m_workspace(std::move(workspace))
, m_session(std::move(session)) {
}
UIEditorWorkspaceControllerValidationResult UIEditorWorkspaceController::ValidateState() const {
const UIEditorPanelRegistryValidationResult registryValidation =
ValidateUIEditorPanelRegistry(m_panelRegistry);
if (!registryValidation.IsValid()) {
UIEditorWorkspaceControllerValidationResult result = {};
result.code = UIEditorWorkspaceControllerValidationCode::InvalidPanelRegistry;
result.message = registryValidation.message;
return result;
}
const UIEditorWorkspaceValidationResult workspaceValidation =
ValidateUIEditorWorkspace(m_workspace);
if (!workspaceValidation.IsValid()) {
UIEditorWorkspaceControllerValidationResult result = {};
result.code = UIEditorWorkspaceControllerValidationCode::InvalidWorkspace;
result.message = workspaceValidation.message;
return result;
}
const UIEditorWorkspaceSessionValidationResult sessionValidation =
ValidateUIEditorWorkspaceSession(m_panelRegistry, m_workspace, m_session);
if (!sessionValidation.IsValid()) {
UIEditorWorkspaceControllerValidationResult result = {};
result.code = UIEditorWorkspaceControllerValidationCode::InvalidWorkspaceSession;
result.message = sessionValidation.message;
return result;
}
return {};
}
UIEditorWorkspaceCommandResult UIEditorWorkspaceController::BuildResult(
const UIEditorWorkspaceCommand& command,
UIEditorWorkspaceCommandStatus status,
std::string message) const {
UIEditorWorkspaceCommandResult result = {};
result.kind = command.kind;
result.status = status;
result.panelId = command.panelId;
result.message = std::move(message);
result.activePanelId = m_workspace.activePanelId;
result.visiblePanelIds = CollectVisiblePanelIds(m_workspace, m_session);
return result;
}
UIEditorWorkspaceCommandResult UIEditorWorkspaceController::FinalizeMutation(
const UIEditorWorkspaceCommand& command,
bool changed,
std::string changedMessage,
std::string unexpectedFailureMessage,
const UIEditorWorkspaceModel& previousWorkspace,
const UIEditorWorkspaceSession& previousSession) {
if (!changed) {
return BuildResult(
command,
UIEditorWorkspaceCommandStatus::Rejected,
std::move(unexpectedFailureMessage));
}
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
if (!validation.IsValid()) {
m_workspace = previousWorkspace;
m_session = previousSession;
return BuildResult(
command,
UIEditorWorkspaceCommandStatus::Rejected,
"Command produced invalid workspace state: " + validation.message);
}
return BuildResult(
command,
UIEditorWorkspaceCommandStatus::Changed,
std::move(changedMessage));
}
const UIEditorPanelDescriptor* UIEditorWorkspaceController::FindPanelDescriptor(
std::string_view panelId) const {
return FindUIEditorPanelDescriptor(m_panelRegistry, panelId);
}
UIEditorWorkspaceCommandResult UIEditorWorkspaceController::Dispatch(
const UIEditorWorkspaceCommand& command) {
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
if (command.kind != UIEditorWorkspaceCommandKind::ResetWorkspace &&
!validation.IsValid()) {
return BuildResult(
command,
UIEditorWorkspaceCommandStatus::Rejected,
"Controller state invalid: " + validation.message);
}
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
const UIEditorWorkspaceSession previousSession = m_session;
const UIEditorPanelSessionState* panelState =
command.kind == UIEditorWorkspaceCommandKind::ResetWorkspace
? nullptr
: FindUIEditorPanelSessionState(m_session, command.panelId);
const UIEditorPanelDescriptor* panelDescriptor =
command.kind == UIEditorWorkspaceCommandKind::ResetWorkspace
? nullptr
: FindPanelDescriptor(command.panelId);
switch (command.kind) {
case UIEditorWorkspaceCommandKind::OpenPanel:
if (command.panelId.empty()) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "OpenPanel requires a panelId.");
}
if (panelDescriptor == nullptr || panelState == nullptr) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "OpenPanel target panel is missing.");
}
if (panelState->open && panelState->visible) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already open and visible.");
}
return FinalizeMutation(
command,
TryOpenUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId),
"Panel opened and activated.",
"OpenPanel failed unexpectedly.",
previousWorkspace,
previousSession);
case UIEditorWorkspaceCommandKind::ClosePanel:
if (command.panelId.empty()) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ClosePanel requires a panelId.");
}
if (panelDescriptor == nullptr || panelState == nullptr) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ClosePanel target panel is missing.");
}
if (!panelDescriptor->canClose) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Panel cannot be closed.");
}
if (!panelState->open) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already closed.");
}
return FinalizeMutation(
command,
TryCloseUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId),
"Panel closed.",
"ClosePanel failed unexpectedly.",
previousWorkspace,
previousSession);
case UIEditorWorkspaceCommandKind::ShowPanel:
if (command.panelId.empty()) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ShowPanel requires a panelId.");
}
if (panelDescriptor == nullptr || panelState == nullptr) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ShowPanel target panel is missing.");
}
if (!panelState->open) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Closed panel must be opened before it can be shown.");
}
if (panelState->visible) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already visible.");
}
return FinalizeMutation(
command,
TryShowUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId),
"Panel shown and activated.",
"ShowPanel failed unexpectedly.",
previousWorkspace,
previousSession);
case UIEditorWorkspaceCommandKind::HidePanel:
if (command.panelId.empty()) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "HidePanel requires a panelId.");
}
if (panelDescriptor == nullptr || panelState == nullptr) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "HidePanel target panel is missing.");
}
if (!panelDescriptor->canHide) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Panel cannot be hidden.");
}
if (!panelState->open) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Closed panel cannot be hidden.");
}
if (!panelState->visible) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already hidden.");
}
return FinalizeMutation(
command,
TryHideUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId),
"Panel hidden and active panel re-resolved.",
"HidePanel failed unexpectedly.",
previousWorkspace,
previousSession);
case UIEditorWorkspaceCommandKind::ActivatePanel:
if (command.panelId.empty()) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ActivatePanel requires a panelId.");
}
if (panelDescriptor == nullptr || panelState == nullptr) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ActivatePanel target panel is missing.");
}
if (!panelState->open || !panelState->visible) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Only open and visible panels can be activated.");
}
if (m_workspace.activePanelId == command.panelId) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already active.");
}
return FinalizeMutation(
command,
TryActivateUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId),
"Panel activated.",
"ActivatePanel failed unexpectedly.",
previousWorkspace,
previousSession);
case UIEditorWorkspaceCommandKind::ResetWorkspace:
if (AreWorkspaceModelsEquivalent(m_workspace, m_baselineWorkspace) &&
AreWorkspaceSessionsEquivalent(m_session, m_baselineSession)) {
return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Workspace already matches the baseline state.");
}
m_workspace = m_baselineWorkspace;
m_session = m_baselineSession;
return FinalizeMutation(
command,
true,
"Workspace reset to baseline.",
"ResetWorkspace failed unexpectedly.",
previousWorkspace,
previousSession);
}
return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Unknown command kind.");
}
UIEditorWorkspaceController BuildDefaultUIEditorWorkspaceController(
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace) {
return UIEditorWorkspaceController(
panelRegistry,
workspace,
BuildDefaultUIEditorWorkspaceSession(panelRegistry, workspace));
}
} // namespace XCEngine::NewEditor

View File

@@ -1,3 +1,4 @@
#include <XCNewEditor/Editor/UIEditorPanelRegistry.h>
#include <XCNewEditor/Editor/UIEditorWorkspaceModel.h>
#include <cmath>
@@ -21,6 +22,18 @@ bool IsValidSplitRatio(float value) {
return std::isfinite(value) && value > 0.0f && value < 1.0f;
}
const UIEditorPanelDescriptor& RequirePanelDescriptor(
const UIEditorPanelRegistry& registry,
std::string_view panelId) {
if (const UIEditorPanelDescriptor* descriptor = FindUIEditorPanelDescriptor(registry, panelId);
descriptor != nullptr) {
return *descriptor;
}
static const UIEditorPanelDescriptor fallbackDescriptor = {};
return fallbackDescriptor;
}
const UIEditorWorkspacePanelState* FindPanelRecursive(
const UIEditorWorkspaceNode& node,
std::string_view panelId) {
@@ -192,6 +205,21 @@ UIEditorWorkspaceValidationResult ValidateNodeRecursive(
} // namespace
UIEditorWorkspaceModel BuildDefaultEditorShellWorkspaceModel() {
const UIEditorPanelRegistry registry = BuildDefaultEditorShellPanelRegistry();
const UIEditorPanelDescriptor& rootPanel =
RequirePanelDescriptor(registry, "editor-foundation-root");
UIEditorWorkspaceModel workspace = {};
workspace.root = BuildUIEditorWorkspacePanel(
"editor-foundation-root-node",
rootPanel.panelId,
rootPanel.defaultTitle,
rootPanel.placeholder);
workspace.activePanelId = rootPanel.panelId;
return workspace;
}
UIEditorWorkspaceNode BuildUIEditorWorkspacePanel(
std::string nodeId,
std::string panelId,

View File

@@ -0,0 +1,476 @@
#include <XCNewEditor/Editor/UIEditorWorkspaceSession.h>
#include <unordered_set>
#include <utility>
namespace XCEngine::NewEditor {
namespace {
UIEditorWorkspaceSessionValidationResult MakeValidationError(
UIEditorWorkspaceSessionValidationCode code,
std::string message) {
UIEditorWorkspaceSessionValidationResult result = {};
result.code = code;
result.message = std::move(message);
return result;
}
UIEditorPanelSessionState* FindMutablePanelSessionState(
UIEditorWorkspaceSession& session,
std::string_view panelId) {
for (UIEditorPanelSessionState& state : session.panelStates) {
if (state.panelId == panelId) {
return &state;
}
}
return nullptr;
}
const UIEditorPanelDescriptor* FindPanelDescriptor(
const UIEditorPanelRegistry& panelRegistry,
std::string_view panelId) {
return FindUIEditorPanelDescriptor(panelRegistry, panelId);
}
const UIEditorWorkspacePanelState* FindPanelRecursive(
const UIEditorWorkspaceNode& node,
std::string_view panelId) {
if (node.kind == UIEditorWorkspaceNodeKind::Panel) {
return node.panel.panelId == panelId ? &node.panel : nullptr;
}
for (const UIEditorWorkspaceNode& child : node.children) {
if (const UIEditorWorkspacePanelState* found = FindPanelRecursive(child, panelId)) {
return found;
}
}
return nullptr;
}
void CollectWorkspacePanelIdsRecursive(
const UIEditorWorkspaceNode& node,
std::vector<std::string>& outPanelIds) {
if (node.kind == UIEditorWorkspaceNodeKind::Panel) {
outPanelIds.push_back(node.panel.panelId);
return;
}
for (const UIEditorWorkspaceNode& child : node.children) {
CollectWorkspacePanelIdsRecursive(child, outPanelIds);
}
}
bool IsPanelOpenAndVisible(
const UIEditorWorkspaceSession& session,
std::string_view panelId) {
const UIEditorPanelSessionState* state = FindUIEditorPanelSessionState(session, panelId);
return state != nullptr && state->open && state->visible;
}
bool IsPanelSelectable(
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session,
std::string_view panelId) {
return !panelId.empty() &&
IsPanelOpenAndVisible(session, panelId) &&
ContainsUIEditorWorkspacePanel(workspace, panelId);
}
std::size_t ResolveVisibleTabIndex(
const UIEditorWorkspaceNode& node,
const UIEditorWorkspaceSession& session) {
if (node.kind != UIEditorWorkspaceNodeKind::TabStack || node.children.empty()) {
return node.selectedTabIndex;
}
if (node.selectedTabIndex < node.children.size()) {
const UIEditorWorkspaceNode& selectedChild = node.children[node.selectedTabIndex];
if (selectedChild.kind == UIEditorWorkspaceNodeKind::Panel &&
IsPanelOpenAndVisible(session, selectedChild.panel.panelId)) {
return node.selectedTabIndex;
}
}
for (std::size_t index = 0; index < node.children.size(); ++index) {
const UIEditorWorkspaceNode& child = node.children[index];
if (child.kind == UIEditorWorkspaceNodeKind::Panel &&
IsPanelOpenAndVisible(session, child.panel.panelId)) {
return index;
}
}
return node.children.size();
}
void CollectVisiblePanelsRecursive(
const UIEditorWorkspaceNode& node,
const UIEditorWorkspaceSession& session,
std::string_view activePanelId,
std::vector<UIEditorWorkspaceVisiblePanel>& outPanels) {
switch (node.kind) {
case UIEditorWorkspaceNodeKind::Panel: {
if (!IsPanelOpenAndVisible(session, node.panel.panelId)) {
return;
}
UIEditorWorkspaceVisiblePanel panel = {};
panel.panelId = node.panel.panelId;
panel.title = node.panel.title;
panel.active = node.panel.panelId == activePanelId;
panel.placeholder = node.panel.placeholder;
outPanels.push_back(std::move(panel));
return;
}
case UIEditorWorkspaceNodeKind::TabStack: {
const std::size_t resolvedIndex = ResolveVisibleTabIndex(node, session);
if (resolvedIndex < node.children.size()) {
CollectVisiblePanelsRecursive(
node.children[resolvedIndex],
session,
activePanelId,
outPanels);
}
return;
}
case UIEditorWorkspaceNodeKind::Split:
for (const UIEditorWorkspaceNode& child : node.children) {
CollectVisiblePanelsRecursive(child, session, activePanelId, outPanels);
}
return;
}
}
void NormalizeSessionStatesAgainstRegistry(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceSession& session) {
for (UIEditorPanelSessionState& state : session.panelStates) {
if (!state.open) {
state.visible = false;
}
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, state.panelId);
if (descriptor == nullptr) {
continue;
}
if (!descriptor->canClose) {
state.open = true;
state.visible = true;
continue;
}
if (!descriptor->canHide && state.open) {
state.visible = true;
}
}
}
void NormalizeWorkspaceSession(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view preferredActivePanelId) {
NormalizeSessionStatesAgainstRegistry(panelRegistry, session);
std::string targetActivePanelId = {};
if (IsPanelSelectable(workspace, session, preferredActivePanelId)) {
targetActivePanelId = std::string(preferredActivePanelId);
} else if (IsPanelSelectable(workspace, session, workspace.activePanelId)) {
targetActivePanelId = workspace.activePanelId;
} else {
const std::vector<UIEditorWorkspaceVisiblePanel> visiblePanels =
CollectUIEditorWorkspaceVisiblePanels(workspace, session);
if (!visiblePanels.empty()) {
targetActivePanelId = visiblePanels.front().panelId;
}
}
if (targetActivePanelId.empty()) {
workspace.activePanelId.clear();
return;
}
TryActivateUIEditorWorkspacePanel(workspace, targetActivePanelId);
}
bool AreWorkspaceNodesEquivalent(
const UIEditorWorkspaceNode& lhs,
const UIEditorWorkspaceNode& rhs) {
if (lhs.kind != rhs.kind ||
lhs.nodeId != rhs.nodeId ||
lhs.splitAxis != rhs.splitAxis ||
lhs.splitRatio != rhs.splitRatio ||
lhs.selectedTabIndex != rhs.selectedTabIndex ||
lhs.panel.panelId != rhs.panel.panelId ||
lhs.panel.title != rhs.panel.title ||
lhs.panel.placeholder != rhs.panel.placeholder ||
lhs.children.size() != rhs.children.size()) {
return false;
}
for (std::size_t index = 0; index < lhs.children.size(); ++index) {
if (!AreWorkspaceNodesEquivalent(lhs.children[index], rhs.children[index])) {
return false;
}
}
return true;
}
bool AreWorkspaceModelsEquivalent(
const UIEditorWorkspaceModel& lhs,
const UIEditorWorkspaceModel& rhs) {
return lhs.activePanelId == rhs.activePanelId &&
AreWorkspaceNodesEquivalent(lhs.root, rhs.root);
}
bool AreWorkspaceSessionsEquivalent(
const UIEditorWorkspaceSession& lhs,
const UIEditorWorkspaceSession& rhs) {
if (lhs.panelStates.size() != rhs.panelStates.size()) {
return false;
}
for (std::size_t index = 0; index < lhs.panelStates.size(); ++index) {
const UIEditorPanelSessionState& lhsState = lhs.panelStates[index];
const UIEditorPanelSessionState& rhsState = rhs.panelStates[index];
if (lhsState.panelId != rhsState.panelId ||
lhsState.open != rhsState.open ||
lhsState.visible != rhsState.visible) {
return false;
}
}
return true;
}
} // namespace
UIEditorWorkspaceSession BuildDefaultUIEditorWorkspaceSession(
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace) {
UIEditorWorkspaceSession session = {};
std::vector<std::string> panelIds = {};
CollectWorkspacePanelIdsRecursive(workspace.root, panelIds);
session.panelStates.reserve(panelIds.size());
for (std::string& panelId : panelIds) {
UIEditorPanelSessionState state = {};
state.panelId = std::move(panelId);
session.panelStates.push_back(std::move(state));
}
NormalizeSessionStatesAgainstRegistry(panelRegistry, session);
return session;
}
const UIEditorPanelSessionState* FindUIEditorPanelSessionState(
const UIEditorWorkspaceSession& session,
std::string_view panelId) {
for (const UIEditorPanelSessionState& state : session.panelStates) {
if (state.panelId == panelId) {
return &state;
}
}
return nullptr;
}
UIEditorWorkspaceSessionValidationResult ValidateUIEditorWorkspaceSession(
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session) {
std::vector<std::string> workspacePanelIds = {};
CollectWorkspacePanelIdsRecursive(workspace.root, workspacePanelIds);
std::unordered_set<std::string> expectedPanelIds = {};
expectedPanelIds.insert(workspacePanelIds.begin(), workspacePanelIds.end());
std::unordered_set<std::string> seenPanelIds = {};
for (const UIEditorPanelSessionState& state : session.panelStates) {
if (!seenPanelIds.insert(state.panelId).second) {
return MakeValidationError(
UIEditorWorkspaceSessionValidationCode::DuplicatePanelId,
"Workspace session contains duplicated panel state '" + state.panelId + "'.");
}
if (!expectedPanelIds.contains(state.panelId)) {
return MakeValidationError(
UIEditorWorkspaceSessionValidationCode::UnknownPanelId,
"Workspace session state '" + state.panelId + "' is not present in the workspace tree.");
}
if (!state.open && state.visible) {
return MakeValidationError(
UIEditorWorkspaceSessionValidationCode::ClosedPanelVisible,
"Workspace session state '" + state.panelId + "' cannot be visible while closed.");
}
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, state.panelId);
if (descriptor == nullptr) {
return MakeValidationError(
UIEditorWorkspaceSessionValidationCode::UnknownPanelId,
"Workspace session state '" + state.panelId + "' is missing from the panel registry.");
}
if (!descriptor->canClose && !state.open) {
return MakeValidationError(
UIEditorWorkspaceSessionValidationCode::NonCloseablePanelClosed,
"Workspace session state '" + state.panelId + "' cannot be closed.");
}
if (!descriptor->canHide && state.open && !state.visible) {
return MakeValidationError(
UIEditorWorkspaceSessionValidationCode::NonHideablePanelHidden,
"Workspace session state '" + state.panelId + "' cannot be hidden.");
}
}
for (const std::string& panelId : workspacePanelIds) {
if (!seenPanelIds.contains(panelId)) {
return MakeValidationError(
UIEditorWorkspaceSessionValidationCode::MissingPanelState,
"Workspace panel '" + panelId + "' is missing from the workspace session.");
}
}
if (!workspace.activePanelId.empty() &&
FindUIEditorWorkspaceActivePanel(workspace, session) == nullptr) {
return MakeValidationError(
UIEditorWorkspaceSessionValidationCode::InvalidActivePanelId,
"Active panel id '" + workspace.activePanelId + "' is missing, closed, or hidden.");
}
return {};
}
std::vector<UIEditorWorkspaceVisiblePanel> CollectUIEditorWorkspaceVisiblePanels(
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session) {
std::vector<UIEditorWorkspaceVisiblePanel> visiblePanels = {};
CollectVisiblePanelsRecursive(workspace.root, session, workspace.activePanelId, visiblePanels);
return visiblePanels;
}
const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel(
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session) {
if (workspace.activePanelId.empty() ||
!IsPanelOpenAndVisible(session, workspace.activePanelId)) {
return nullptr;
}
const std::vector<UIEditorWorkspaceVisiblePanel> visiblePanels =
CollectUIEditorWorkspaceVisiblePanels(workspace, session);
for (const UIEditorWorkspaceVisiblePanel& panel : visiblePanels) {
if (panel.panelId == workspace.activePanelId) {
return FindPanelRecursive(workspace.root, workspace.activePanelId);
}
}
return nullptr;
}
bool TryOpenUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId) {
const UIEditorWorkspaceModel workspaceBefore = workspace;
const UIEditorWorkspaceSession sessionBefore = session;
UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId);
if (state == nullptr) {
return false;
}
state->open = true;
state->visible = true;
NormalizeWorkspaceSession(panelRegistry, workspace, session, panelId);
return !AreWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
!AreWorkspaceSessionsEquivalent(sessionBefore, session);
}
bool TryCloseUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId) {
const UIEditorWorkspaceModel workspaceBefore = workspace;
const UIEditorWorkspaceSession sessionBefore = session;
UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId);
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, panelId);
if (state == nullptr || descriptor == nullptr || !descriptor->canClose) {
return false;
}
state->open = false;
state->visible = false;
NormalizeWorkspaceSession(panelRegistry, workspace, session, {});
return !AreWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
!AreWorkspaceSessionsEquivalent(sessionBefore, session);
}
bool TryShowUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId) {
const UIEditorWorkspaceModel workspaceBefore = workspace;
const UIEditorWorkspaceSession sessionBefore = session;
UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId);
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, panelId);
if (state == nullptr || descriptor == nullptr || !state->open || !descriptor->canHide) {
return false;
}
state->visible = true;
NormalizeWorkspaceSession(panelRegistry, workspace, session, panelId);
return !AreWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
!AreWorkspaceSessionsEquivalent(sessionBefore, session);
}
bool TryHideUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId) {
const UIEditorWorkspaceModel workspaceBefore = workspace;
const UIEditorWorkspaceSession sessionBefore = session;
UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId);
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, panelId);
if (state == nullptr || descriptor == nullptr || !state->open || !descriptor->canHide) {
return false;
}
state->visible = false;
NormalizeWorkspaceSession(panelRegistry, workspace, session, {});
return !AreWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
!AreWorkspaceSessionsEquivalent(sessionBefore, session);
}
bool TryActivateUIEditorWorkspacePanel(
const UIEditorPanelRegistry& panelRegistry,
UIEditorWorkspaceModel& workspace,
UIEditorWorkspaceSession& session,
std::string_view panelId) {
const UIEditorWorkspaceModel workspaceBefore = workspace;
const UIEditorWorkspaceSession sessionBefore = session;
if (!IsPanelSelectable(workspace, session, panelId)) {
return false;
}
NormalizeWorkspaceSession(panelRegistry, workspace, session, panelId);
return !AreWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
!AreWorkspaceSessionsEquivalent(sessionBefore, session);
}
} // namespace XCEngine::NewEditor

View File

@@ -1,15 +1,7 @@
<Theme name="EditorShellTheme">
<Tokens>
<Color name="color.bg.workspace" value="#1C1C1C" />
<Color name="color.bg.panel" value="#292929" />
<Color name="color.bg.accent" value="#3A3A3A" />
<Color name="color.bg.selection" value="#4A4A4A" />
<Color name="color.text.primary" value="#EEEEEE" />
<Color name="color.text.muted" value="#B0B0B0" />
<Spacing name="space.panel" value="12" />
<Spacing name="space.shell" value="18" />
<Radius name="radius.panel" value="10" />
<Radius name="radius.control" value="8" />
<Color name="color.bg.workspace" value="#16181C" />
<Spacing name="space.shell" value="14" />
</Tokens>
<Widgets>
@@ -17,16 +9,5 @@
<Property name="background" value="color.bg.workspace" />
<Property name="padding" value="space.shell" />
</Widget>
<Widget type="Card" style="EditorPanel">
<Property name="background" value="color.bg.panel" />
<Property name="radius" value="radius.panel" />
<Property name="padding" value="space.panel" />
</Widget>
<Widget type="Button" style="EditorChip">
<Property name="background" value="color.bg.selection" />
<Property name="radius" value="radius.control" />
</Widget>
</Widgets>
</Theme>

View File

@@ -1,8 +1,9 @@
<View
name="NewEditorShell"
theme="../themes/editor_shell.xctheme">
theme="../themes/editor_shell.xctheme"
style="EditorWorkspace">
<Column
id="editor-shell-root"
id="editor-foundation-root"
width="fill"
height="fill" />
</View>