Extract new editor host command session bridge

This commit is contained in:
2026-04-12 01:49:08 +08:00
parent 838f676fa6
commit 7ad4bfbb1c
10 changed files with 344 additions and 84 deletions

View File

@@ -147,7 +147,9 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP)
add_executable(XCUIEditorApp WIN32
app/main.cpp
app/Application.cpp
app/Commands/ProductEditorHostCommandBridge.cpp
app/Core/ProductEditorContext.cpp
app/Core/ProductEditorSession.cpp
app/Icons/ProductBuiltInIcons.cpp
app/Panels/ProductHierarchyPanel.cpp
app/Panels/ProductProjectPanel.cpp

View File

@@ -377,7 +377,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
InitializeUIEditorRuntimeTrace(logRoot);
SetUnhandledExceptionFilter(&Application::HandleUnhandledException);
LogRuntimeTrace("app", "initialize begin");
if (!m_editorContext.Initialize(repoRoot, *this)) {
if (!m_editorContext.Initialize(repoRoot)) {
LogRuntimeTrace(
"app",
"shell asset validation failed: " + m_editorContext.GetValidationMessage());
@@ -417,6 +417,11 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
m_windowDpi = QueryWindowDpi(m_hwnd);
m_dpiScale = GetDpiScale();
m_renderer.SetDpiScale(m_dpiScale);
m_editorContext.SetExitRequestHandler([this]() {
if (m_hwnd != nullptr) {
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
}
});
std::ostringstream dpiTrace = {};
dpiTrace << "initial dpi=" << m_windowDpi << " scale=" << m_dpiScale;
@@ -811,69 +816,6 @@ LONG WINAPI Application::HandleUnhandledException(EXCEPTION_POINTERS* exceptionI
return EXCEPTION_EXECUTE_HANDLER;
}
UIEditorHostCommandEvaluationResult Application::EvaluateHostCommand(
std::string_view commandId) const {
UIEditorHostCommandEvaluationResult result = {};
if (commandId == "file.exit") {
result.executable = true;
result.message = "Exit command is ready.";
return result;
}
if (commandId == "help.about") {
result.executable = true;
result.message = "About placeholder is ready.";
return result;
}
if (commandId.rfind("file.", 0u) == 0u) {
result.message = "Document command bridge is not attached yet.";
return result;
}
if (commandId.rfind("edit.", 0u) == 0u) {
result.message = "Edit route bridge is not attached yet.";
return result;
}
if (commandId.rfind("assets.", 0u) == 0u) {
result.message = "Asset pipeline bridge is not attached yet.";
return result;
}
if (commandId.rfind("run.", 0u) == 0u) {
result.message = "Runtime bridge is not attached yet.";
return result;
}
if (commandId.rfind("scripts.", 0u) == 0u) {
result.message = "Script pipeline bridge is not attached yet.";
return result;
}
result.message = "Host command is not attached yet.";
return result;
}
UIEditorHostCommandDispatchResult Application::DispatchHostCommand(
std::string_view commandId) {
UIEditorHostCommandDispatchResult result = {};
if (commandId == "file.exit") {
result.commandExecuted = true;
result.message = "Exit requested.";
if (m_hwnd != nullptr) {
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
}
return result;
}
if (commandId == "help.about") {
result.commandExecuted = true;
result.message = "About dialog will be wired after modal layer lands.";
m_editorContext.SetStatus("About", result.message);
return result;
}
result.message = "Host command dispatch rejected.";
return result;
}
LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_NCCREATE) {
TryEnableNonClientDpiScaling(hwnd);

View File

@@ -24,17 +24,12 @@
namespace XCEngine::UI::Editor {
class Application : public UIEditorHostCommandHandler {
class Application {
public:
Application() = default;
int Run(HINSTANCE hInstance, int nCmdShow);
UIEditorHostCommandEvaluationResult EvaluateHostCommand(
std::string_view commandId) const override;
UIEditorHostCommandDispatchResult DispatchHostCommand(
std::string_view commandId) override;
private:
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

View File

@@ -0,0 +1,156 @@
#include "ProductEditorHostCommandBridge.h"
#include <string>
namespace XCEngine::UI::Editor::App {
void ProductEditorHostCommandBridge::BindSession(ProductEditorSession& session) {
m_session = &session;
}
void ProductEditorHostCommandBridge::SetExitRequestHandler(std::function<void()> handler) {
m_requestExit = std::move(handler);
}
UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::EvaluateHostCommand(
std::string_view commandId) const {
UIEditorHostCommandEvaluationResult result = {};
if (commandId == "file.exit") {
result.executable = true;
result.message = "Exit command is ready.";
return result;
}
if (commandId == "help.about") {
result.executable = true;
result.message = "About placeholder is ready.";
return result;
}
if (commandId.rfind("edit.", 0u) == 0u) {
return EvaluateEditCommand(commandId);
}
if (commandId.rfind("file.", 0u) == 0u) {
return BuildDisabledResult("Document command bridge is not attached yet.");
}
if (commandId.rfind("assets.", 0u) == 0u) {
return BuildDisabledResult("Asset pipeline bridge is not attached yet.");
}
if (commandId.rfind("run.", 0u) == 0u) {
return BuildDisabledResult("Runtime bridge is not attached yet.");
}
if (commandId.rfind("scripts.", 0u) == 0u) {
return BuildDisabledResult("Script pipeline bridge is not attached yet.");
}
return BuildDisabledResult("Host command is not attached yet.");
}
UIEditorHostCommandDispatchResult ProductEditorHostCommandBridge::DispatchHostCommand(
std::string_view commandId) {
UIEditorHostCommandDispatchResult result = {};
if (commandId == "file.exit") {
result.commandExecuted = true;
result.message = "Exit requested.";
if (m_requestExit) {
m_requestExit();
}
return result;
}
if (commandId == "help.about") {
result.commandExecuted = true;
result.message = "About dialog will be wired after modal layer lands.";
return result;
}
if (commandId.rfind("edit.", 0u) == 0u) {
return DispatchEditCommand(commandId);
}
result.message = EvaluateHostCommand(commandId).message;
return result;
}
UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::BuildDisabledResult(
std::string_view message) const {
UIEditorHostCommandEvaluationResult result = {};
result.executable = false;
result.message = std::string(message);
return result;
}
UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::EvaluateEditCommand(
std::string_view commandId) const {
if (m_session == nullptr) {
return BuildDisabledResult("Editor session is not attached yet.");
}
switch (m_session->activeRoute) {
case ProductEditorActionRoute::Hierarchy:
if (SupportsHierarchyEditCommands(commandId)) {
return UIEditorHostCommandEvaluationResult{
true,
"Hierarchy edit route placeholder is active."
};
}
return BuildDisabledResult("Current hierarchy route does not expose this command yet.");
case ProductEditorActionRoute::Project:
if (SupportsProjectEditCommands(commandId)) {
return UIEditorHostCommandEvaluationResult{
true,
"Project edit route placeholder is active."
};
}
return BuildDisabledResult("Current project route does not expose this command yet.");
case ProductEditorActionRoute::None:
return BuildDisabledResult("No active edit route.");
default:
return BuildDisabledResult("Current panel does not expose edit commands yet.");
}
}
UIEditorHostCommandDispatchResult ProductEditorHostCommandBridge::DispatchEditCommand(
std::string_view commandId) {
UIEditorHostCommandDispatchResult result = {};
const UIEditorHostCommandEvaluationResult evaluation = EvaluateEditCommand(commandId);
if (!evaluation.executable) {
result.message = evaluation.message;
return result;
}
result.commandExecuted = true;
switch (m_session != nullptr ? m_session->activeRoute : ProductEditorActionRoute::None) {
case ProductEditorActionRoute::Hierarchy:
result.message = "Hierarchy edit command route reached.";
break;
case ProductEditorActionRoute::Project:
result.message = "Project edit command route reached.";
break;
default:
result.message = "Edit command route reached.";
break;
}
return result;
}
bool ProductEditorHostCommandBridge::SupportsHierarchyEditCommands(
std::string_view commandId) const {
return commandId == "edit.cut" ||
commandId == "edit.copy" ||
commandId == "edit.paste" ||
commandId == "edit.duplicate" ||
commandId == "edit.delete" ||
commandId == "edit.rename";
}
bool ProductEditorHostCommandBridge::SupportsProjectEditCommands(
std::string_view commandId) const {
return commandId == "edit.delete" ||
commandId == "edit.rename";
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -0,0 +1,36 @@
#pragma once
#include "Core/ProductEditorSession.h"
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
#include <functional>
#include <string_view>
namespace XCEngine::UI::Editor::App {
class ProductEditorHostCommandBridge : public UIEditorHostCommandHandler {
public:
void BindSession(ProductEditorSession& session);
void SetExitRequestHandler(std::function<void()> handler);
UIEditorHostCommandEvaluationResult EvaluateHostCommand(
std::string_view commandId) const override;
UIEditorHostCommandDispatchResult DispatchHostCommand(
std::string_view commandId) override;
private:
UIEditorHostCommandEvaluationResult BuildDisabledResult(
std::string_view message) const;
UIEditorHostCommandEvaluationResult EvaluateEditCommand(
std::string_view commandId) const;
UIEditorHostCommandDispatchResult DispatchEditCommand(
std::string_view commandId);
bool SupportsHierarchyEditCommands(std::string_view commandId) const;
bool SupportsProjectEditCommands(std::string_view commandId) const;
ProductEditorSession* m_session = nullptr;
std::function<void()> m_requestExit = {};
};
} // namespace XCEngine::UI::Editor::App

View File

@@ -28,21 +28,24 @@ std::string ComposeStatusText(
} // namespace
bool ProductEditorContext::Initialize(
const std::filesystem::path& repoRoot,
UIEditorHostCommandHandler& hostCommandHandler) {
bool ProductEditorContext::Initialize(const std::filesystem::path& repoRoot) {
m_shellAsset = BuildProductShellAsset(repoRoot);
m_shellValidation = ValidateEditorShellAsset(m_shellAsset);
if (!m_shellValidation.IsValid()) {
return false;
}
m_session = {};
m_session.repoRoot = repoRoot;
m_session.projectRoot = (repoRoot / "project").lexically_normal();
m_workspaceController = UIEditorWorkspaceController(
m_shellAsset.panelRegistry,
m_shellAsset.workspace,
m_shellAsset.workspaceSession);
SyncSessionFromWorkspace();
m_hostCommandBridge.BindSession(m_session);
m_shortcutManager = BuildEditorShellShortcutManager(m_shellAsset);
m_shortcutManager.SetHostCommandHandler(&hostCommandHandler);
m_shortcutManager.SetHostCommandHandler(&m_hostCommandBridge);
m_shellServices = {};
m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher();
m_shellServices.shortcutManager = &m_shortcutManager;
@@ -55,6 +58,14 @@ void ProductEditorContext::AttachTextMeasurer(
m_shellServices.textMeasurer = &textMeasurer;
}
void ProductEditorContext::SetExitRequestHandler(std::function<void()> handler) {
m_hostCommandBridge.SetExitRequestHandler(std::move(handler));
}
void ProductEditorContext::SyncSessionFromWorkspace() {
SyncProductEditorSessionFromWorkspace(m_session, m_workspaceController);
}
bool ProductEditorContext::IsValid() const {
return m_shellValidation.IsValid();
}
@@ -67,6 +78,10 @@ const EditorShellAsset& ProductEditorContext::GetShellAsset() const {
return m_shellAsset;
}
const ProductEditorSession& ProductEditorContext::GetSession() const {
return m_session;
}
UIEditorWorkspaceController& ProductEditorContext::GetWorkspaceController() {
return m_workspaceController;
}

View File

@@ -1,33 +1,31 @@
#pragma once
#include "Commands/ProductEditorHostCommandBridge.h"
#include "Core/ProductEditorSession.h"
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include <XCEditor/Shell/UIEditorShellAsset.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <filesystem>
#include <functional>
#include <string>
#include <string_view>
namespace XCEngine::UI::Editor {
class UIEditorHostCommandHandler;
struct UIEditorTextMeasurer;
} // namespace XCEngine::UI::Editor
namespace XCEngine::UI::Editor::App {
class ProductEditorContext {
public:
bool Initialize(
const std::filesystem::path& repoRoot,
UIEditorHostCommandHandler& hostCommandHandler);
bool Initialize(const std::filesystem::path& repoRoot);
void AttachTextMeasurer(const UIEditorTextMeasurer& textMeasurer);
void SetExitRequestHandler(std::function<void()> handler);
void SyncSessionFromWorkspace();
bool IsValid() const;
const std::string& GetValidationMessage() const;
const EditorShellAsset& GetShellAsset() const;
const ProductEditorSession& GetSession() const;
UIEditorWorkspaceController& GetWorkspaceController();
const UIEditorWorkspaceController& GetWorkspaceController() const;
@@ -48,6 +46,8 @@ private:
UIEditorWorkspaceController m_workspaceController = {};
UIEditorShortcutManager m_shortcutManager = {};
UIEditorShellInteractionServices m_shellServices = {};
ProductEditorSession m_session = {};
ProductEditorHostCommandBridge m_hostCommandBridge = {};
std::string m_lastStatus = {};
std::string m_lastMessage = {};
};

View File

@@ -0,0 +1,67 @@
#include "ProductEditorSession.h"
namespace XCEngine::UI::Editor::App {
std::string_view GetProductEditorRuntimeModeName(ProductEditorRuntimeMode mode) {
switch (mode) {
case ProductEditorRuntimeMode::Edit:
return "Edit";
case ProductEditorRuntimeMode::Play:
return "Play";
case ProductEditorRuntimeMode::Paused:
return "Paused";
default:
return "Unknown";
}
}
std::string_view GetProductEditorActionRouteName(ProductEditorActionRoute route) {
switch (route) {
case ProductEditorActionRoute::Hierarchy:
return "Hierarchy";
case ProductEditorActionRoute::Project:
return "Project";
case ProductEditorActionRoute::Inspector:
return "Inspector";
case ProductEditorActionRoute::Console:
return "Console";
case ProductEditorActionRoute::Scene:
return "Scene";
case ProductEditorActionRoute::Game:
return "Game";
case ProductEditorActionRoute::None:
default:
return "None";
}
}
ProductEditorActionRoute ResolveProductEditorActionRoute(std::string_view panelId) {
if (panelId == "hierarchy") {
return ProductEditorActionRoute::Hierarchy;
}
if (panelId == "project") {
return ProductEditorActionRoute::Project;
}
if (panelId == "inspector") {
return ProductEditorActionRoute::Inspector;
}
if (panelId == "console") {
return ProductEditorActionRoute::Console;
}
if (panelId == "scene") {
return ProductEditorActionRoute::Scene;
}
if (panelId == "game") {
return ProductEditorActionRoute::Game;
}
return ProductEditorActionRoute::None;
}
void SyncProductEditorSessionFromWorkspace(
ProductEditorSession& session,
const UIEditorWorkspaceController& controller) {
session.activePanelId = controller.GetWorkspace().activePanelId;
session.activeRoute = ResolveProductEditorActionRoute(session.activePanelId);
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -0,0 +1,45 @@
#pragma once
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <cstdint>
#include <filesystem>
#include <string>
#include <string_view>
namespace XCEngine::UI::Editor::App {
enum class ProductEditorRuntimeMode : std::uint8_t {
Edit = 0,
Play,
Paused
};
enum class ProductEditorActionRoute : std::uint8_t {
None = 0,
Hierarchy,
Project,
Inspector,
Console,
Scene,
Game
};
struct ProductEditorSession {
std::filesystem::path repoRoot = {};
std::filesystem::path projectRoot = {};
std::string activePanelId = {};
ProductEditorRuntimeMode runtimeMode = ProductEditorRuntimeMode::Edit;
ProductEditorActionRoute activeRoute = ProductEditorActionRoute::None;
};
std::string_view GetProductEditorRuntimeModeName(ProductEditorRuntimeMode mode);
std::string_view GetProductEditorActionRouteName(ProductEditorActionRoute route);
ProductEditorActionRoute ResolveProductEditorActionRoute(std::string_view panelId);
void SyncProductEditorSessionFromWorkspace(
ProductEditorSession& session,
const UIEditorWorkspaceController& controller);
} // namespace XCEngine::UI::Editor::App

View File

@@ -99,6 +99,7 @@ void ProductEditorWorkspace::Update(
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
std::string_view captureText) {
const auto& metrics = ResolveUIEditorShellInteractionMetrics();
context.SyncSessionFromWorkspace();
const UIEditorShellInteractionDefinition definition =
context.BuildShellDefinition(captureText);
const std::vector<UIInputEvent> hostedContentEvents = inputEvents;
@@ -115,6 +116,7 @@ void ProductEditorWorkspace::Update(
shellEvents,
context.GetShellServices(),
metrics);
context.SyncSessionFromWorkspace();
context.UpdateStatusFromShellResult(m_shellFrame.result);
const std::string& activePanelId =