Refactor new editor app context and workspace shell

This commit is contained in:
2026-04-12 01:29:00 +08:00
parent 0ff02150c0
commit 838f676fa6
9 changed files with 1177 additions and 303 deletions

View File

@@ -1,7 +1,5 @@
#include "Application.h"
#include "Shell/ProductShellAsset.h"
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
@@ -33,53 +31,12 @@ using ::XCEngine::UI::UIInputEventType;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIPointerButton;
using ::XCEngine::UI::UIRect;
using App::BuildProductShellAsset;
using App::BuildProductShellInteractionDefinition;
constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost";
constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor";
constexpr UINT kDefaultDpi = 96u;
constexpr float kBaseDpiScale = 96.0f;
UIEditorShellComposeModel BuildShellComposeModelFromFrame(
const UIEditorShellInteractionFrame& frame) {
UIEditorShellComposeModel model = {};
model.menuBarItems = frame.request.menuBarItems;
model.toolbarButtons = frame.model.toolbarButtons;
model.statusSegments = frame.model.statusSegments;
model.workspacePresentations = frame.model.workspacePresentations;
return model;
}
void AppendShellPopups(
UIDrawList& drawList,
const UIEditorShellInteractionFrame& frame,
const UIEditorShellInteractionPalette& palette,
const UIEditorShellInteractionMetrics& metrics) {
const std::size_t popupCount =
(std::min)(frame.request.popupRequests.size(), frame.popupFrames.size());
for (std::size_t index = 0; index < popupCount; ++index) {
const UIEditorShellInteractionPopupRequest& popupRequest =
frame.request.popupRequests[index];
const UIEditorShellInteractionPopupFrame& popupFrame =
frame.popupFrames[index];
Widgets::AppendUIEditorMenuPopupBackground(
drawList,
popupRequest.layout,
popupRequest.widgetItems,
popupFrame.popupState,
palette.popupPalette,
metrics.popupMetrics);
Widgets::AppendUIEditorMenuPopupForeground(
drawList,
popupRequest.layout,
popupRequest.widgetItems,
popupFrame.popupState,
palette.popupPalette,
metrics.popupMetrics);
}
}
Application* GetApplicationFromWindow(HWND hwnd) {
return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
}
@@ -387,27 +344,6 @@ std::string DescribeProjectPanelEvent(const App::ProductProjectPanel::Event& eve
return stream.str();
}
std::vector<UIInputEvent> FilterShellInputEventsForHostedContentCapture(
const std::vector<UIInputEvent>& inputEvents) {
std::vector<UIInputEvent> filteredEvents = {};
filteredEvents.reserve(inputEvents.size());
for (const UIInputEvent& event : inputEvents) {
switch (event.type) {
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerLeave:
case UIInputEventType::PointerButtonDown:
case UIInputEventType::PointerButtonUp:
case UIInputEventType::PointerWheel:
break;
default:
filteredEvents.push_back(event);
break;
}
}
return filteredEvents;
}
} // namespace
int Application::Run(HINSTANCE hInstance, int nCmdShow) {
@@ -441,28 +377,13 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
InitializeUIEditorRuntimeTrace(logRoot);
SetUnhandledExceptionFilter(&Application::HandleUnhandledException);
LogRuntimeTrace("app", "initialize begin");
m_shellAsset = BuildProductShellAsset(repoRoot);
m_shellValidation = ValidateEditorShellAsset(m_shellAsset);
m_validationMessage = m_shellValidation.message;
if (!m_shellValidation.IsValid()) {
LogRuntimeTrace("app", "shell asset validation failed: " + m_validationMessage);
if (!m_editorContext.Initialize(repoRoot, *this)) {
LogRuntimeTrace(
"app",
"shell asset validation failed: " + m_editorContext.GetValidationMessage());
return false;
}
m_workspaceController = UIEditorWorkspaceController(
m_shellAsset.panelRegistry,
m_shellAsset.workspace,
m_shellAsset.workspaceSession);
m_shortcutManager = BuildEditorShellShortcutManager(m_shellAsset);
m_shortcutManager.SetHostCommandHandler(this);
m_shellServices = {};
m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher();
m_shellServices.shortcutManager = &m_shortcutManager;
m_shellServices.textMeasurer = &m_renderer;
m_lastStatus = "Ready";
m_lastMessage = "Old editor shell baseline loaded.";
LogRuntimeTrace("app", "workspace initialized: " + DescribeWorkspaceState());
WNDCLASSEXW windowClass = {};
windowClass.cbSize = sizeof(windowClass);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
@@ -505,24 +426,23 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
LogRuntimeTrace("app", "renderer initialization failed");
return false;
}
m_builtInIcons.Initialize(m_renderer, repoRoot);
if (!m_builtInIcons.GetLastError().empty()) {
LogRuntimeTrace("icons", m_builtInIcons.GetLastError());
m_editorContext.AttachTextMeasurer(m_renderer);
m_editorWorkspace.Initialize(repoRoot, m_renderer);
if (!m_editorWorkspace.GetBuiltInIconError().empty()) {
LogRuntimeTrace("icons", m_editorWorkspace.GetBuiltInIconError());
}
m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons);
m_projectPanel.SetBuiltInIcons(&m_builtInIcons);
m_hierarchyPanel.Initialize();
m_projectPanel.SetTextMeasurer(&m_renderer);
m_projectPanel.Initialize(repoRoot);
LogRuntimeTrace(
"app",
"workspace initialized: " +
m_editorContext.DescribeWorkspaceState(m_editorWorkspace.GetShellInteractionState()));
ShowWindow(m_hwnd, nCmdShow);
UpdateWindow(m_hwnd);
m_autoScreenshot.Initialize(m_shellAsset.captureRootPath);
m_autoScreenshot.Initialize(m_editorContext.GetShellAsset().captureRootPath);
if (IsAutoCaptureOnStartupEnabled()) {
m_autoScreenshot.RequestCapture("startup");
m_lastStatus = "Capture";
m_lastMessage = "Startup capture requested.";
m_editorContext.SetStatus("Capture", "Startup capture requested.");
}
LogRuntimeTrace("app", "initialize completed");
return true;
@@ -535,7 +455,7 @@ void Application::Shutdown() {
}
m_autoScreenshot.Shutdown();
m_builtInIcons.Shutdown();
m_editorWorkspace.Shutdown();
m_renderer.Shutdown();
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
@@ -572,80 +492,50 @@ void Application::RenderFrame() {
UIRect(0.0f, 0.0f, width, height),
UIColor(0.10f, 0.10f, 0.10f, 1.0f));
if (m_shellValidation.IsValid()) {
const auto& metrics = ResolveUIEditorShellInteractionMetrics();
const auto& palette = ResolveUIEditorShellInteractionPalette();
const UIEditorShellInteractionDefinition definition = BuildShellDefinition();
if (m_editorContext.IsValid()) {
std::vector<UIInputEvent> frameEvents = std::move(m_pendingInputEvents);
m_pendingInputEvents.clear();
const std::vector<UIInputEvent> hostedContentEvents = frameEvents;
const std::vector<UIInputEvent> shellEvents =
m_projectPanel.HasActivePointerCapture()
? FilterShellInputEventsForHostedContentCapture(frameEvents)
: frameEvents;
if (!frameEvents.empty()) {
LogRuntimeTrace(
"input",
DescribeInputEvents(frameEvents) + " | " + DescribeWorkspaceState());
DescribeInputEvents(frameEvents) + " | " +
m_editorContext.DescribeWorkspaceState(
m_editorWorkspace.GetShellInteractionState()));
}
m_shellFrame = UpdateUIEditorShellInteraction(
m_shellInteractionState,
m_workspaceController,
m_editorWorkspace.Update(
m_editorContext,
UIRect(0.0f, 0.0f, width, height),
definition,
shellEvents,
m_shellServices,
metrics);
if (!shellEvents.empty() ||
m_shellFrame.result.workspaceResult.dockHostResult.layoutChanged ||
m_shellFrame.result.workspaceResult.dockHostResult.commandExecuted) {
frameEvents,
BuildCaptureStatusText());
const UIEditorShellInteractionFrame& shellFrame =
m_editorWorkspace.GetShellFrame();
if (!frameEvents.empty() ||
shellFrame.result.workspaceResult.dockHostResult.layoutChanged ||
shellFrame.result.workspaceResult.dockHostResult.commandExecuted) {
std::ostringstream frameTrace = {};
frameTrace << "result consumed="
<< (m_shellFrame.result.consumed ? "true" : "false")
<< (shellFrame.result.consumed ? "true" : "false")
<< " layoutChanged="
<< (m_shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" : "false")
<< (shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" : "false")
<< " commandExecuted="
<< (m_shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false")
<< (shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false")
<< " active="
<< m_workspaceController.GetWorkspace().activePanelId
<< m_editorContext.GetWorkspaceController().GetWorkspace().activePanelId
<< " message="
<< m_shellFrame.result.workspaceResult.dockHostResult.layoutResult.message;
<< shellFrame.result.workspaceResult.dockHostResult.layoutResult.message;
LogRuntimeTrace(
"frame",
frameTrace.str());
}
ApplyHostCaptureRequests(m_shellFrame.result);
UpdateLastStatus(m_shellFrame.result);
m_hierarchyPanel.Update(
m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame,
hostedContentEvents,
!m_shellFrame.result.workspaceInputSuppressed,
m_workspaceController.GetWorkspace().activePanelId == "hierarchy");
m_projectPanel.Update(
m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame,
hostedContentEvents,
!m_shellFrame.result.workspaceInputSuppressed,
m_workspaceController.GetWorkspace().activePanelId == "project");
for (const App::ProductProjectPanel::Event& event : m_projectPanel.GetFrameEvents()) {
ApplyHostCaptureRequests(shellFrame.result);
for (const App::ProductProjectPanel::Event& event : m_editorWorkspace.GetProjectPanelEvents()) {
LogRuntimeTrace("project", DescribeProjectPanelEvent(event));
m_lastStatus = "Project";
m_lastMessage = DescribeProjectPanelEvent(event);
m_editorContext.SetStatus("Project", DescribeProjectPanelEvent(event));
}
ApplyHostedContentCaptureRequests();
ApplyCurrentCursor();
const UIEditorShellComposeModel shellComposeModel =
BuildShellComposeModelFromFrame(m_shellFrame);
AppendUIEditorShellCompose(
drawList,
m_shellFrame.shellFrame,
shellComposeModel,
m_shellInteractionState.composeState,
palette.shellPalette,
metrics.shellMetrics);
m_hierarchyPanel.Append(drawList);
m_projectPanel.Append(drawList);
AppendShellPopups(drawList, m_shellFrame, palette, metrics);
m_editorWorkspace.Append(drawList);
} else {
drawList.AddText(
UIPoint(28.0f, 28.0f),
@@ -654,7 +544,9 @@ void Application::RenderFrame() {
16.0f);
drawList.AddText(
UIPoint(28.0f, 54.0f),
m_validationMessage.empty() ? std::string("Unknown validation error.") : m_validationMessage,
m_editorContext.GetValidationMessage().empty()
? std::string("Unknown validation error.")
: m_editorContext.GetValidationMessage(),
UIColor(0.72f, 0.72f, 0.72f, 1.0f),
12.0f);
}
@@ -695,7 +587,7 @@ bool Application::IsPointerInsideClientArea() const {
}
LPCWSTR Application::ResolveCurrentCursorResource() const {
switch (m_projectPanel.GetCursorKind()) {
switch (m_editorWorkspace.GetHostedContentCursorKind()) {
case App::ProductProjectPanel::CursorKind::ResizeEW:
return IDC_SIZEWE;
case App::ProductProjectPanel::CursorKind::Arrow:
@@ -703,8 +595,7 @@ LPCWSTR Application::ResolveCurrentCursorResource() const {
break;
}
switch (Widgets::ResolveUIEditorDockHostCursorKind(
m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout)) {
switch (m_editorWorkspace.GetDockCursorKind()) {
case Widgets::UIEditorDockHostCursorKind::ResizeEW:
return IDC_SIZEWE;
case Widgets::UIEditorDockHostCursorKind::ResizeNS:
@@ -735,38 +626,28 @@ UIPoint Application::ConvertClientPixelsToDips(LONG x, LONG y) const {
PixelsToDips(static_cast<float>(y)));
}
std::string Application::BuildCaptureStatusText() const {
if (m_autoScreenshot.HasPendingCapture()) {
return "Shot pending...";
}
if (!m_autoScreenshot.GetLastCaptureError().empty()) {
return TruncateText(m_autoScreenshot.GetLastCaptureError(), 38u);
}
if (!m_autoScreenshot.GetLastCaptureSummary().empty()) {
return TruncateText(m_autoScreenshot.GetLastCaptureSummary(), 38u);
}
return {};
}
void Application::LogRuntimeTrace(
std::string_view channel,
std::string_view message) const {
AppendUIEditorRuntimeTrace(channel, message);
}
std::string Application::DescribeWorkspaceState() const {
std::ostringstream stream = {};
stream << "active=" << m_workspaceController.GetWorkspace().activePanelId;
const auto visiblePanels =
CollectUIEditorWorkspaceVisiblePanels(
m_workspaceController.GetWorkspace(),
m_workspaceController.GetSession());
stream << " visible=[";
for (std::size_t index = 0; index < visiblePanels.size(); ++index) {
if (index > 0u) {
stream << ',';
}
stream << visiblePanels[index].panelId;
}
stream << ']';
const auto& dockState =
m_shellInteractionState.workspaceInteractionState.dockHostInteractionState;
stream << " dragNode=" << dockState.activeTabDragNodeId;
stream << " dragPanel=" << dockState.activeTabDragPanelId;
if (dockState.dockHostState.dropPreview.visible) {
stream << " dropTarget=" << dockState.dockHostState.dropPreview.targetNodeId;
}
return stream.str();
}
std::string Application::DescribeInputEvents(
const std::vector<UIInputEvent>& events) const {
std::ostringstream stream = {};
@@ -825,116 +706,19 @@ void Application::ApplyHostCaptureRequests(const UIEditorShellInteractionResult&
}
void Application::ApplyHostedContentCaptureRequests() {
if (m_projectPanel.WantsHostPointerCapture() && GetCapture() != m_hwnd) {
if (m_editorWorkspace.WantsHostPointerCapture() && GetCapture() != m_hwnd) {
SetCapture(m_hwnd);
}
if (m_projectPanel.WantsHostPointerRelease() &&
if (m_editorWorkspace.WantsHostPointerRelease() &&
GetCapture() == m_hwnd &&
!HasShellInteractiveCaptureState()) {
!m_editorWorkspace.HasShellInteractiveCapture()) {
ReleaseCapture();
}
}
bool Application::HasShellInteractiveCaptureState() const {
if (m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) {
return true;
}
if (!m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.activeTabDragNodeId.empty()) {
return true;
}
for (const auto& panelState : m_shellInteractionState.workspaceInteractionState.composeState.panelStates) {
if (panelState.viewportShellState.inputBridgeState.captured) {
return true;
}
}
return false;
}
bool Application::HasInteractiveCaptureState() const {
return HasShellInteractiveCaptureState() || m_projectPanel.HasActivePointerCapture();
}
UIEditorShellInteractionDefinition Application::BuildShellDefinition() const {
std::string statusText = m_lastStatus;
if (!m_lastMessage.empty()) {
statusText += statusText.empty() ? m_lastMessage : ": " + m_lastMessage;
}
std::string captureText = {};
if (m_autoScreenshot.HasPendingCapture()) {
captureText = "Shot pending...";
} else if (!m_autoScreenshot.GetLastCaptureError().empty()) {
captureText = TruncateText(m_autoScreenshot.GetLastCaptureError(), 38u);
} else if (!m_autoScreenshot.GetLastCaptureSummary().empty()) {
captureText = TruncateText(m_autoScreenshot.GetLastCaptureSummary(), 38u);
}
return BuildProductShellInteractionDefinition(
m_shellAsset,
m_workspaceController,
statusText,
captureText);
}
void Application::UpdateLastStatus(const UIEditorShellInteractionResult& result) {
if (result.commandDispatched) {
m_lastStatus = std::string(GetUIEditorCommandDispatchStatusName(result.commandDispatchResult.status));
m_lastMessage = result.commandDispatchResult.message.empty()
? result.commandDispatchResult.displayName
: result.commandDispatchResult.message;
return;
}
if (result.workspaceResult.dockHostResult.layoutChanged) {
m_lastStatus = "Layout";
m_lastMessage = result.workspaceResult.dockHostResult.layoutResult.message;
return;
}
if (result.workspaceResult.dockHostResult.commandExecuted) {
m_lastStatus = "Workspace";
m_lastMessage = result.workspaceResult.dockHostResult.commandResult.message;
return;
}
if (!result.viewportPanelId.empty()) {
m_lastStatus = result.viewportPanelId;
if (result.viewportInputFrame.captureStarted) {
m_lastMessage = "Viewport capture started.";
} else if (result.viewportInputFrame.captureEnded) {
m_lastMessage = "Viewport capture ended.";
} else if (result.viewportInputFrame.focusGained) {
m_lastMessage = "Viewport focused.";
} else if (result.viewportInputFrame.focusLost) {
m_lastMessage = "Viewport focus lost.";
} else if (result.viewportInputFrame.pointerPressedInside) {
m_lastMessage = "Viewport pointer down.";
} else if (result.viewportInputFrame.pointerReleasedInside) {
m_lastMessage = "Viewport pointer up.";
} else if (result.viewportInputFrame.pointerMoved) {
m_lastMessage = "Viewport pointer move.";
} else if (result.viewportInputFrame.wheelDelta != 0.0f) {
m_lastMessage = "Viewport wheel.";
}
return;
}
if (result.menuMutation.changed) {
if (!result.itemId.empty() && !result.menuMutation.openedPopupId.empty()) {
m_lastStatus = "Menu";
m_lastMessage = result.itemId + " opened child popup.";
} else if (!result.menuId.empty() && !result.menuMutation.openedPopupId.empty()) {
m_lastStatus = "Menu";
m_lastMessage = result.menuId + " opened.";
} else {
m_lastStatus = "Menu";
m_lastMessage = "Popup chain dismissed.";
}
}
return m_editorWorkspace.HasInteractiveCapture();
}
void Application::QueuePointerEvent(
@@ -1082,8 +866,7 @@ UIEditorHostCommandDispatchResult Application::DispatchHostCommand(
if (commandId == "help.about") {
result.commandExecuted = true;
result.message = "About dialog will be wired after modal layer lands.";
m_lastStatus = "About";
m_lastMessage = result.message;
m_editorContext.SetStatus("About", result.message);
return result;
}

View File

@@ -8,14 +8,10 @@
#include <Host/InputModifierTracker.h>
#include <Host/NativeRenderer.h>
#include "Icons/ProductBuiltInIcons.h"
#include "Panels/ProductHierarchyPanel.h"
#include "Panels/ProductProjectPanel.h"
#include "Core/ProductEditorContext.h"
#include "Workspace/ProductEditorWorkspace.h"
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include <XCEditor/Shell/UIEditorShellAsset.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <windows.h>
#include <windowsx.h>
@@ -53,14 +49,11 @@ private:
float GetDpiScale() const;
float PixelsToDips(float pixels) const;
::XCEngine::UI::UIPoint ConvertClientPixelsToDips(LONG x, LONG y) const;
std::string BuildCaptureStatusText() const;
void LogRuntimeTrace(std::string_view channel, std::string_view message) const;
void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result);
void ApplyHostedContentCaptureRequests();
bool HasShellInteractiveCaptureState() const;
bool HasInteractiveCaptureState() const;
UIEditorShellInteractionDefinition BuildShellDefinition() const;
void UpdateLastStatus(const UIEditorShellInteractionResult& result);
std::string DescribeWorkspaceState() const;
std::string DescribeInputEvents(
const std::vector<::XCEngine::UI::UIInputEvent>& events) const;
void QueuePointerEvent(
@@ -82,21 +75,10 @@ private:
::XCEngine::UI::Editor::Host::NativeRenderer m_renderer = {};
::XCEngine::UI::Editor::Host::AutoScreenshotController m_autoScreenshot = {};
::XCEngine::UI::Editor::Host::InputModifierTracker m_inputModifierTracker = {};
EditorShellAsset m_shellAsset = {};
EditorShellAssetValidationResult m_shellValidation = {};
UIEditorWorkspaceController m_workspaceController = {};
UIEditorShortcutManager m_shortcutManager = {};
App::ProductBuiltInIcons m_builtInIcons = {};
App::ProductHierarchyPanel m_hierarchyPanel = {};
App::ProductProjectPanel m_projectPanel = {};
UIEditorShellInteractionServices m_shellServices = {};
UIEditorShellInteractionState m_shellInteractionState = {};
UIEditorShellInteractionFrame m_shellFrame = {};
App::ProductEditorContext m_editorContext = {};
App::ProductEditorWorkspace m_editorWorkspace = {};
std::vector<::XCEngine::UI::UIInputEvent> m_pendingInputEvents = {};
bool m_trackingMouseLeave = false;
std::string m_validationMessage = {};
std::string m_lastStatus = {};
std::string m_lastMessage = {};
UINT m_windowDpi = 96u;
float m_dpiScale = 1.0f;
};

View File

@@ -0,0 +1,187 @@
#include "ProductEditorContext.h"
#include "Shell/ProductShellAsset.h"
#include <algorithm>
#include <sstream>
#include <utility>
namespace XCEngine::UI::Editor::App {
namespace {
using ::XCEngine::UI::Editor::BuildEditorShellShortcutManager;
std::string ComposeStatusText(
std::string_view status,
std::string_view message) {
if (status.empty()) {
return std::string(message);
}
if (message.empty()) {
return std::string(status);
}
return std::string(status) + ": " + std::string(message);
}
} // namespace
bool ProductEditorContext::Initialize(
const std::filesystem::path& repoRoot,
UIEditorHostCommandHandler& hostCommandHandler) {
m_shellAsset = BuildProductShellAsset(repoRoot);
m_shellValidation = ValidateEditorShellAsset(m_shellAsset);
if (!m_shellValidation.IsValid()) {
return false;
}
m_workspaceController = UIEditorWorkspaceController(
m_shellAsset.panelRegistry,
m_shellAsset.workspace,
m_shellAsset.workspaceSession);
m_shortcutManager = BuildEditorShellShortcutManager(m_shellAsset);
m_shortcutManager.SetHostCommandHandler(&hostCommandHandler);
m_shellServices = {};
m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher();
m_shellServices.shortcutManager = &m_shortcutManager;
SetReadyStatus();
return true;
}
void ProductEditorContext::AttachTextMeasurer(
const UIEditorTextMeasurer& textMeasurer) {
m_shellServices.textMeasurer = &textMeasurer;
}
bool ProductEditorContext::IsValid() const {
return m_shellValidation.IsValid();
}
const std::string& ProductEditorContext::GetValidationMessage() const {
return m_shellValidation.message;
}
const EditorShellAsset& ProductEditorContext::GetShellAsset() const {
return m_shellAsset;
}
UIEditorWorkspaceController& ProductEditorContext::GetWorkspaceController() {
return m_workspaceController;
}
const UIEditorWorkspaceController& ProductEditorContext::GetWorkspaceController() const {
return m_workspaceController;
}
const UIEditorShellInteractionServices& ProductEditorContext::GetShellServices() const {
return m_shellServices;
}
UIEditorShellInteractionDefinition ProductEditorContext::BuildShellDefinition(
std::string_view captureText) const {
return BuildProductShellInteractionDefinition(
m_shellAsset,
m_workspaceController,
ComposeStatusText(m_lastStatus, m_lastMessage),
captureText);
}
void ProductEditorContext::SetReadyStatus() {
SetStatus("Ready", "Old editor shell baseline loaded.");
}
void ProductEditorContext::SetStatus(
std::string status,
std::string message) {
m_lastStatus = std::move(status);
m_lastMessage = std::move(message);
}
void ProductEditorContext::UpdateStatusFromShellResult(
const UIEditorShellInteractionResult& result) {
if (result.commandDispatched) {
SetStatus(
std::string(GetUIEditorCommandDispatchStatusName(result.commandDispatchResult.status)),
result.commandDispatchResult.message.empty()
? result.commandDispatchResult.displayName
: result.commandDispatchResult.message);
return;
}
if (result.workspaceResult.dockHostResult.layoutChanged) {
SetStatus("Layout", result.workspaceResult.dockHostResult.layoutResult.message);
return;
}
if (result.workspaceResult.dockHostResult.commandExecuted) {
SetStatus("Workspace", result.workspaceResult.dockHostResult.commandResult.message);
return;
}
if (!result.viewportPanelId.empty()) {
std::string message = {};
if (result.viewportInputFrame.captureStarted) {
message = "Viewport capture started.";
} else if (result.viewportInputFrame.captureEnded) {
message = "Viewport capture ended.";
} else if (result.viewportInputFrame.focusGained) {
message = "Viewport focused.";
} else if (result.viewportInputFrame.focusLost) {
message = "Viewport focus lost.";
} else if (result.viewportInputFrame.pointerPressedInside) {
message = "Viewport pointer down.";
} else if (result.viewportInputFrame.pointerReleasedInside) {
message = "Viewport pointer up.";
} else if (result.viewportInputFrame.pointerMoved) {
message = "Viewport pointer move.";
} else if (result.viewportInputFrame.wheelDelta != 0.0f) {
message = "Viewport wheel.";
}
if (!message.empty()) {
SetStatus(result.viewportPanelId, std::move(message));
}
return;
}
if (result.menuMutation.changed) {
if (!result.itemId.empty() && !result.menuMutation.openedPopupId.empty()) {
SetStatus("Menu", result.itemId + " opened child popup.");
} else if (!result.menuId.empty() && !result.menuMutation.openedPopupId.empty()) {
SetStatus("Menu", result.menuId + " opened.");
} else {
SetStatus("Menu", "Popup chain dismissed.");
}
}
}
std::string ProductEditorContext::DescribeWorkspaceState(
const UIEditorShellInteractionState& interactionState) const {
std::ostringstream stream = {};
stream << "active=" << m_workspaceController.GetWorkspace().activePanelId;
const auto visiblePanels =
CollectUIEditorWorkspaceVisiblePanels(
m_workspaceController.GetWorkspace(),
m_workspaceController.GetSession());
stream << " visible=[";
for (std::size_t index = 0; index < visiblePanels.size(); ++index) {
if (index > 0u) {
stream << ',';
}
stream << visiblePanels[index].panelId;
}
stream << ']';
const auto& dockState =
interactionState.workspaceInteractionState.dockHostInteractionState;
stream << " dragNode=" << dockState.activeTabDragNodeId;
stream << " dragPanel=" << dockState.activeTabDragPanelId;
if (dockState.dockHostState.dropPreview.visible) {
stream << " dropTarget=" << dockState.dockHostState.dropPreview.targetNodeId;
}
return stream.str();
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -0,0 +1,55 @@
#pragma once
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include <XCEditor/Shell/UIEditorShellAsset.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <filesystem>
#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);
void AttachTextMeasurer(const UIEditorTextMeasurer& textMeasurer);
bool IsValid() const;
const std::string& GetValidationMessage() const;
const EditorShellAsset& GetShellAsset() const;
UIEditorWorkspaceController& GetWorkspaceController();
const UIEditorWorkspaceController& GetWorkspaceController() const;
const UIEditorShellInteractionServices& GetShellServices() const;
UIEditorShellInteractionDefinition BuildShellDefinition(
std::string_view captureText) const;
void SetReadyStatus();
void SetStatus(std::string status, std::string message);
void UpdateStatusFromShellResult(const UIEditorShellInteractionResult& result);
std::string DescribeWorkspaceState(
const UIEditorShellInteractionState& interactionState) const;
private:
EditorShellAsset m_shellAsset = {};
EditorShellAssetValidationResult m_shellValidation = {};
UIEditorWorkspaceController m_workspaceController = {};
UIEditorShortcutManager m_shortcutManager = {};
UIEditorShellInteractionServices m_shellServices = {};
std::string m_lastStatus = {};
std::string m_lastMessage = {};
};
} // namespace XCEngine::UI::Editor::App

View File

@@ -0,0 +1,210 @@
#include "ProductEditorWorkspace.h"
#include <XCEditor/Shell/UIEditorShellCompose.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <algorithm>
namespace XCEngine::UI::Editor::App {
namespace {
using ::XCEngine::UI::UIColor;
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIInputEvent;
using ::XCEngine::UI::UIInputEventType;
UIEditorShellComposeModel BuildShellComposeModelFromFrame(
const UIEditorShellInteractionFrame& frame) {
UIEditorShellComposeModel model = {};
model.menuBarItems = frame.request.menuBarItems;
model.toolbarButtons = frame.model.toolbarButtons;
model.statusSegments = frame.model.statusSegments;
model.workspacePresentations = frame.model.workspacePresentations;
return model;
}
void AppendShellPopups(
UIDrawList& drawList,
const UIEditorShellInteractionFrame& frame,
const UIEditorShellInteractionPalette& palette,
const UIEditorShellInteractionMetrics& metrics) {
const std::size_t popupCount =
(std::min)(frame.request.popupRequests.size(), frame.popupFrames.size());
for (std::size_t index = 0; index < popupCount; ++index) {
const UIEditorShellInteractionPopupRequest& popupRequest =
frame.request.popupRequests[index];
const UIEditorShellInteractionPopupFrame& popupFrame =
frame.popupFrames[index];
Widgets::AppendUIEditorMenuPopupBackground(
drawList,
popupRequest.layout,
popupRequest.widgetItems,
popupFrame.popupState,
palette.popupPalette,
metrics.popupMetrics);
Widgets::AppendUIEditorMenuPopupForeground(
drawList,
popupRequest.layout,
popupRequest.widgetItems,
popupFrame.popupState,
palette.popupPalette,
metrics.popupMetrics);
}
}
std::vector<UIInputEvent> FilterShellInputEventsForHostedContentCapture(
const std::vector<UIInputEvent>& inputEvents) {
std::vector<UIInputEvent> filteredEvents = {};
filteredEvents.reserve(inputEvents.size());
for (const UIInputEvent& event : inputEvents) {
switch (event.type) {
case UIInputEventType::PointerMove:
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerLeave:
case UIInputEventType::PointerButtonDown:
case UIInputEventType::PointerButtonUp:
case UIInputEventType::PointerWheel:
break;
default:
filteredEvents.push_back(event);
break;
}
}
return filteredEvents;
}
} // namespace
void ProductEditorWorkspace::Initialize(
const std::filesystem::path& repoRoot,
Host::NativeRenderer& renderer) {
m_builtInIcons.Initialize(renderer, repoRoot);
m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons);
m_projectPanel.SetBuiltInIcons(&m_builtInIcons);
m_projectPanel.SetTextMeasurer(&renderer);
m_hierarchyPanel.Initialize();
m_projectPanel.Initialize(repoRoot);
}
void ProductEditorWorkspace::Shutdown() {
m_shellFrame = {};
m_shellInteractionState = {};
m_builtInIcons.Shutdown();
}
void ProductEditorWorkspace::Update(
ProductEditorContext& context,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
std::string_view captureText) {
const auto& metrics = ResolveUIEditorShellInteractionMetrics();
const UIEditorShellInteractionDefinition definition =
context.BuildShellDefinition(captureText);
const std::vector<UIInputEvent> hostedContentEvents = inputEvents;
const std::vector<UIInputEvent> shellEvents =
HasHostedContentCapture()
? FilterShellInputEventsForHostedContentCapture(inputEvents)
: inputEvents;
m_shellFrame = UpdateUIEditorShellInteraction(
m_shellInteractionState,
context.GetWorkspaceController(),
bounds,
definition,
shellEvents,
context.GetShellServices(),
metrics);
context.UpdateStatusFromShellResult(m_shellFrame.result);
const std::string& activePanelId =
context.GetWorkspaceController().GetWorkspace().activePanelId;
m_hierarchyPanel.Update(
m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame,
hostedContentEvents,
!m_shellFrame.result.workspaceInputSuppressed,
activePanelId == "hierarchy");
m_projectPanel.Update(
m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame,
hostedContentEvents,
!m_shellFrame.result.workspaceInputSuppressed,
activePanelId == "project");
}
void ProductEditorWorkspace::Append(UIDrawList& drawList) const {
const auto& metrics = ResolveUIEditorShellInteractionMetrics();
const auto& palette = ResolveUIEditorShellInteractionPalette();
const UIEditorShellComposeModel shellComposeModel =
BuildShellComposeModelFromFrame(m_shellFrame);
AppendUIEditorShellCompose(
drawList,
m_shellFrame.shellFrame,
shellComposeModel,
m_shellInteractionState.composeState,
palette.shellPalette,
metrics.shellMetrics);
m_hierarchyPanel.Append(drawList);
m_projectPanel.Append(drawList);
AppendShellPopups(drawList, m_shellFrame, palette, metrics);
}
const UIEditorShellInteractionFrame& ProductEditorWorkspace::GetShellFrame() const {
return m_shellFrame;
}
const UIEditorShellInteractionState& ProductEditorWorkspace::GetShellInteractionState() const {
return m_shellInteractionState;
}
const std::vector<ProductProjectPanel::Event>& ProductEditorWorkspace::GetProjectPanelEvents() const {
return m_projectPanel.GetFrameEvents();
}
const std::string& ProductEditorWorkspace::GetBuiltInIconError() const {
return m_builtInIcons.GetLastError();
}
ProductProjectPanel::CursorKind ProductEditorWorkspace::GetHostedContentCursorKind() const {
return m_projectPanel.GetCursorKind();
}
Widgets::UIEditorDockHostCursorKind ProductEditorWorkspace::GetDockCursorKind() const {
return Widgets::ResolveUIEditorDockHostCursorKind(
m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout);
}
bool ProductEditorWorkspace::WantsHostPointerCapture() const {
return m_projectPanel.WantsHostPointerCapture();
}
bool ProductEditorWorkspace::WantsHostPointerRelease() const {
return m_projectPanel.WantsHostPointerRelease();
}
bool ProductEditorWorkspace::HasHostedContentCapture() const {
return m_projectPanel.HasActivePointerCapture();
}
bool ProductEditorWorkspace::HasShellInteractiveCapture() const {
if (m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) {
return true;
}
if (!m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.activeTabDragNodeId.empty()) {
return true;
}
for (const auto& panelState : m_shellInteractionState.workspaceInteractionState.composeState.panelStates) {
if (panelState.viewportShellState.inputBridgeState.captured) {
return true;
}
}
return false;
}
bool ProductEditorWorkspace::HasInteractiveCapture() const {
return HasShellInteractiveCapture() || HasHostedContentCapture();
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -0,0 +1,55 @@
#pragma once
#include "Core/ProductEditorContext.h"
#include "Icons/ProductBuiltInIcons.h"
#include "Panels/ProductHierarchyPanel.h"
#include "Panels/ProductProjectPanel.h"
#include <Host/NativeRenderer.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h>
#include <XCEngine/UI/DrawData.h>
#include <filesystem>
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor::App {
class ProductEditorWorkspace {
public:
void Initialize(
const std::filesystem::path& repoRoot,
Host::NativeRenderer& renderer);
void Shutdown();
void Update(
ProductEditorContext& context,
const ::XCEngine::UI::UIRect& bounds,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
std::string_view captureText);
void Append(::XCEngine::UI::UIDrawList& drawList) const;
const UIEditorShellInteractionFrame& GetShellFrame() const;
const UIEditorShellInteractionState& GetShellInteractionState() const;
const std::vector<ProductProjectPanel::Event>& GetProjectPanelEvents() const;
const std::string& GetBuiltInIconError() const;
ProductProjectPanel::CursorKind GetHostedContentCursorKind() const;
Widgets::UIEditorDockHostCursorKind GetDockCursorKind() const;
bool WantsHostPointerCapture() const;
bool WantsHostPointerRelease() const;
bool HasHostedContentCapture() const;
bool HasShellInteractiveCapture() const;
bool HasInteractiveCapture() const;
private:
ProductBuiltInIcons m_builtInIcons = {};
ProductHierarchyPanel m_hierarchyPanel = {};
ProductProjectPanel m_projectPanel = {};
UIEditorShellInteractionState m_shellInteractionState = {};
UIEditorShellInteractionFrame m_shellFrame = {};
};
} // namespace XCEngine::UI::Editor::App