483 lines
15 KiB
C++
483 lines
15 KiB
C++
#include "Platform/Win32/WindowManager/Internal.h"
|
|
#include "Platform/Win32/WindowManager/EditorWindowMessageDispatcher.h"
|
|
|
|
#include "Bootstrap/EditorResources.h"
|
|
#include "Composition/EditorContext.h"
|
|
#include "Platform/Win32/EditorWindow.h"
|
|
|
|
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
|
#include <XCEditor/Workspace/UIEditorWindowWorkspaceController.h>
|
|
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
EditorWindowManager::EditorWindowManager(
|
|
EditorWindowHostConfig hostConfig,
|
|
std::filesystem::path repoRoot,
|
|
EditorContext& editorContext)
|
|
: m_hostRuntime(std::make_unique<Internal::EditorWindowHostRuntime>(
|
|
hostConfig,
|
|
std::move(repoRoot),
|
|
editorContext)) {
|
|
m_workspaceCoordinator =
|
|
std::make_unique<Internal::EditorWindowWorkspaceCoordinator>(*m_hostRuntime);
|
|
}
|
|
|
|
EditorWindowManager::~EditorWindowManager() = default;
|
|
|
|
EditorWindow* EditorWindowManager::CreateEditorWindow(
|
|
UIEditorWorkspaceController workspaceController,
|
|
const CreateParams& params) {
|
|
EditorWindow* const window =
|
|
m_hostRuntime->CreateEditorWindow(std::move(workspaceController), params);
|
|
if (window != nullptr) {
|
|
m_workspaceCoordinator->RegisterExistingWindow(*window);
|
|
}
|
|
return window;
|
|
}
|
|
|
|
void EditorWindowManager::HandlePendingNativeWindowCreated(HWND hwnd) {
|
|
m_hostRuntime->HandlePendingNativeWindowCreated(hwnd);
|
|
}
|
|
|
|
void EditorWindowManager::Shutdown() {
|
|
m_workspaceCoordinator->EndGlobalTabDragSession();
|
|
m_hostRuntime->Shutdown();
|
|
}
|
|
|
|
bool EditorWindowManager::TryDispatchWindowMessage(
|
|
HWND hwnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
LRESULT& outResult) {
|
|
if (m_hostRuntime == nullptr || m_workspaceCoordinator == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
EditorWindow* const window = m_hostRuntime->FindWindow(hwnd);
|
|
if (window == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
return Internal::EditorWindowMessageDispatcher::TryDispatch(
|
|
hwnd,
|
|
*m_hostRuntime,
|
|
*m_workspaceCoordinator,
|
|
*window,
|
|
message,
|
|
wParam,
|
|
lParam,
|
|
outResult);
|
|
}
|
|
|
|
EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) {
|
|
return m_hostRuntime->FindWindow(hwnd);
|
|
}
|
|
|
|
const EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) const {
|
|
return m_hostRuntime->FindWindow(hwnd);
|
|
}
|
|
|
|
EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) {
|
|
return m_hostRuntime->FindWindow(windowId);
|
|
}
|
|
|
|
const EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) const {
|
|
return m_hostRuntime->FindWindow(windowId);
|
|
}
|
|
|
|
EditorWindow* EditorWindowManager::FindPrimaryWindow() {
|
|
return m_hostRuntime->FindPrimaryWindow();
|
|
}
|
|
|
|
const EditorWindow* EditorWindowManager::FindPrimaryWindow() const {
|
|
return m_hostRuntime->FindPrimaryWindow();
|
|
}
|
|
|
|
bool EditorWindowManager::HasWindows() const {
|
|
return m_hostRuntime->HasWindows();
|
|
}
|
|
|
|
void EditorWindowManager::DestroyClosedWindows() {
|
|
m_hostRuntime->DestroyClosedWindows();
|
|
}
|
|
|
|
void EditorWindowManager::RenderAllWindows() {
|
|
m_hostRuntime->RenderAllWindows(
|
|
m_workspaceCoordinator->IsGlobalTabDragActive(),
|
|
*m_workspaceCoordinator);
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|
|
|
|
namespace XCEngine::UI::Editor::App::Internal {
|
|
|
|
EditorWindowHostRuntime::EditorWindowHostRuntime(
|
|
EditorWindowHostConfig hostConfig,
|
|
std::filesystem::path repoRoot,
|
|
EditorContext& editorContext)
|
|
: m_hostConfig(hostConfig),
|
|
m_repoRoot(std::move(repoRoot)),
|
|
m_editorContext(editorContext) {}
|
|
|
|
EditorWindowHostRuntime::~EditorWindowHostRuntime() = default;
|
|
|
|
EditorWindow* EditorWindowHostRuntime::CreateEditorWindow(
|
|
UIEditorWorkspaceController workspaceController,
|
|
const CreateParams& params) {
|
|
auto windowPtr = std::make_unique<EditorWindow>(
|
|
params.windowId,
|
|
params.title.empty() ? std::wstring(L"XCEngine Editor") : params.title,
|
|
params.primary,
|
|
std::move(workspaceController));
|
|
EditorWindow* const rawWindow = windowPtr.get();
|
|
m_windows.push_back(std::move(windowPtr));
|
|
|
|
const auto eraseRawWindow = [this, rawWindow]() {
|
|
const auto it = std::find_if(
|
|
m_windows.begin(),
|
|
m_windows.end(),
|
|
[rawWindow](const std::unique_ptr<EditorWindow>& candidate) {
|
|
return candidate.get() == rawWindow;
|
|
});
|
|
if (it != m_windows.end()) {
|
|
m_windows.erase(it);
|
|
}
|
|
};
|
|
|
|
m_pendingCreateWindow = rawWindow;
|
|
const HWND hwnd = CreateWindowExW(
|
|
WS_EX_APPWINDOW,
|
|
m_hostConfig.windowClassName,
|
|
rawWindow->GetTitle().c_str(),
|
|
m_hostConfig.windowStyle,
|
|
params.initialX,
|
|
params.initialY,
|
|
params.initialWidth,
|
|
params.initialHeight,
|
|
nullptr,
|
|
nullptr,
|
|
m_hostConfig.hInstance,
|
|
m_hostConfig.windowUserData);
|
|
m_pendingCreateWindow = nullptr;
|
|
if (hwnd == nullptr) {
|
|
eraseRawWindow();
|
|
return nullptr;
|
|
}
|
|
|
|
if (!rawWindow->HasHwnd()) {
|
|
rawWindow->AttachHwnd(hwnd);
|
|
}
|
|
|
|
auto failWindowInitialization = [&](std::string_view message) {
|
|
LogRuntimeTrace("window", std::string(message));
|
|
DestroyEditorWindow(*rawWindow);
|
|
eraseRawWindow();
|
|
return static_cast<EditorWindow*>(nullptr);
|
|
};
|
|
|
|
const HICON bigIcon = static_cast<HICON>(
|
|
LoadImageW(
|
|
m_hostConfig.hInstance,
|
|
MAKEINTRESOURCEW(IDI_APP_ICON),
|
|
IMAGE_ICON,
|
|
0,
|
|
0,
|
|
LR_DEFAULTSIZE));
|
|
const HICON smallIcon = static_cast<HICON>(
|
|
LoadImageW(
|
|
m_hostConfig.hInstance,
|
|
MAKEINTRESOURCEW(IDI_APP_ICON_SMALL),
|
|
IMAGE_ICON,
|
|
GetSystemMetrics(SM_CXSMICON),
|
|
GetSystemMetrics(SM_CYSMICON),
|
|
LR_DEFAULTCOLOR));
|
|
if (bigIcon != nullptr) {
|
|
SendMessageW(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(bigIcon));
|
|
}
|
|
if (smallIcon != nullptr) {
|
|
SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(smallIcon));
|
|
}
|
|
|
|
if (!rawWindow->Initialize(
|
|
m_repoRoot,
|
|
m_editorContext,
|
|
m_editorContext.GetShellAsset().captureRootPath,
|
|
params.autoCaptureOnStartup)) {
|
|
return failWindowInitialization("managed window initialization failed");
|
|
}
|
|
|
|
ShowWindow(hwnd, params.showCommand);
|
|
UpdateWindow(hwnd);
|
|
return rawWindow;
|
|
}
|
|
|
|
void EditorWindowHostRuntime::HandlePendingNativeWindowCreated(HWND hwnd) {
|
|
if (m_pendingCreateWindow != nullptr && !m_pendingCreateWindow->HasHwnd()) {
|
|
m_pendingCreateWindow->AttachHwnd(hwnd);
|
|
}
|
|
}
|
|
|
|
void EditorWindowHostRuntime::Shutdown() {
|
|
for (const std::unique_ptr<EditorWindow>& window : m_windows) {
|
|
if (window != nullptr) {
|
|
DestroyEditorWindow(*window);
|
|
}
|
|
}
|
|
m_windows.clear();
|
|
m_pendingCreateWindow = nullptr;
|
|
}
|
|
|
|
bool EditorWindowHostRuntime::HasWindows() const {
|
|
return !m_windows.empty();
|
|
}
|
|
|
|
void EditorWindowHostRuntime::DestroyEditorWindow(EditorWindow& window) {
|
|
const HWND hwnd = window.GetHwnd();
|
|
window.ForceReleasePointerCapture();
|
|
|
|
window.Shutdown();
|
|
if (hwnd != nullptr && IsWindow(hwnd)) {
|
|
DestroyWindow(hwnd);
|
|
}
|
|
window.MarkDestroyed();
|
|
}
|
|
|
|
void EditorWindowHostRuntime::DestroyClosedWindows() {
|
|
for (auto it = m_windows.begin(); it != m_windows.end();) {
|
|
EditorWindow* const window = it->get();
|
|
if (window == nullptr || window->GetHwnd() != nullptr) {
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
if (m_pendingCreateWindow == window) {
|
|
m_pendingCreateWindow = nullptr;
|
|
}
|
|
|
|
window->Shutdown();
|
|
it = m_windows.erase(it);
|
|
}
|
|
}
|
|
|
|
void EditorWindowHostRuntime::RenderAllWindows(
|
|
bool globalTabDragActive,
|
|
EditorWindowWorkspaceCoordinator& workspaceCoordinator) {
|
|
struct WindowFrameTransferBatch {
|
|
EditorWindow* sourceWindow = nullptr;
|
|
EditorWindowFrameTransferRequests requests = {};
|
|
};
|
|
|
|
std::vector<WindowFrameTransferBatch> transferBatches = {};
|
|
transferBatches.reserve(m_windows.size());
|
|
|
|
for (const std::unique_ptr<EditorWindow>& window : m_windows) {
|
|
if (window == nullptr ||
|
|
window->GetHwnd() == nullptr ||
|
|
window->IsClosing()) {
|
|
continue;
|
|
}
|
|
|
|
EditorWindowFrameTransferRequests transferRequests =
|
|
window->RenderFrame(m_editorContext, globalTabDragActive);
|
|
workspaceCoordinator.CommitWindowProjection(*window);
|
|
if (!transferRequests.HasPendingRequests()) {
|
|
continue;
|
|
}
|
|
|
|
transferBatches.push_back(WindowFrameTransferBatch{
|
|
window.get(),
|
|
std::move(transferRequests),
|
|
});
|
|
}
|
|
|
|
for (WindowFrameTransferBatch& batch : transferBatches) {
|
|
if (batch.sourceWindow == nullptr ||
|
|
batch.sourceWindow->GetHwnd() == nullptr ||
|
|
batch.sourceWindow->IsClosing()) {
|
|
continue;
|
|
}
|
|
|
|
workspaceCoordinator.HandleWindowFrameTransferRequests(
|
|
*batch.sourceWindow,
|
|
std::move(batch.requests));
|
|
}
|
|
}
|
|
|
|
void EditorWindowHostRuntime::HandleDestroyedWindow(HWND hwnd) {
|
|
if (EditorWindow* window = FindWindow(hwnd); window != nullptr) {
|
|
window->MarkDestroyed();
|
|
if (window->IsPrimary()) {
|
|
for (const std::unique_ptr<EditorWindow>& otherWindow : m_windows) {
|
|
if (otherWindow != nullptr &&
|
|
otherWindow.get() != window &&
|
|
otherWindow->GetHwnd() != nullptr &&
|
|
!otherWindow->IsClosing()) {
|
|
otherWindow->MarkClosing();
|
|
PostMessageW(otherWindow->GetHwnd(), WM_CLOSE, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) {
|
|
if (hwnd == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (const std::unique_ptr<EditorWindow>& window : m_windows) {
|
|
if (window != nullptr && window->GetHwnd() == hwnd) {
|
|
return window.get();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) const {
|
|
return const_cast<EditorWindowHostRuntime*>(this)->FindWindow(hwnd);
|
|
}
|
|
|
|
EditorWindow* EditorWindowHostRuntime::FindWindow(std::string_view windowId) {
|
|
if (windowId.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (const std::unique_ptr<EditorWindow>& window : m_windows) {
|
|
if (window != nullptr && window->GetWindowId() == windowId) {
|
|
return window.get();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const EditorWindow* EditorWindowHostRuntime::FindWindow(std::string_view windowId) const {
|
|
return const_cast<EditorWindowHostRuntime*>(this)->FindWindow(windowId);
|
|
}
|
|
|
|
EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() {
|
|
for (const std::unique_ptr<EditorWindow>& window : m_windows) {
|
|
if (window != nullptr && window->IsPrimary()) {
|
|
return window.get();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() const {
|
|
return const_cast<EditorWindowHostRuntime*>(this)->FindPrimaryWindow();
|
|
}
|
|
|
|
void EditorWindowHostRuntime::LogRuntimeTrace(
|
|
std::string_view channel,
|
|
std::string_view message) const {
|
|
AppendUIEditorRuntimeTrace(channel, message);
|
|
}
|
|
|
|
EditorWindowWorkspaceCoordinator::EditorWindowWorkspaceCoordinator(
|
|
EditorWindowHostRuntime& hostRuntime)
|
|
: m_hostRuntime(hostRuntime),
|
|
m_workspaceStore(hostRuntime.GetEditorContext().GetShellAsset().panelRegistry) {}
|
|
|
|
EditorWindowWorkspaceCoordinator::~EditorWindowWorkspaceCoordinator() = default;
|
|
|
|
void EditorWindowWorkspaceCoordinator::RegisterExistingWindow(EditorWindow& window) {
|
|
std::string error = {};
|
|
if (!m_workspaceStore.RegisterWindowProjection(
|
|
window.GetWindowId(),
|
|
window.IsPrimary(),
|
|
window.GetWorkspaceController(),
|
|
error)) {
|
|
LogRuntimeTrace(
|
|
"window",
|
|
"failed to register window '" + std::string(window.GetWindowId()) +
|
|
"' in workspace store: " + error);
|
|
return;
|
|
}
|
|
|
|
RefreshWindowTitle(window);
|
|
}
|
|
|
|
void EditorWindowWorkspaceCoordinator::CommitWindowProjection(EditorWindow& window) {
|
|
std::string error = {};
|
|
if (!m_workspaceStore.CommitWindowProjection(
|
|
window.GetWindowId(),
|
|
window.GetWorkspaceController(),
|
|
error)) {
|
|
if (m_workspaceStore.RegisterWindowProjection(
|
|
window.GetWindowId(),
|
|
window.IsPrimary(),
|
|
window.GetWorkspaceController(),
|
|
error)) {
|
|
RefreshWindowTitle(window);
|
|
return;
|
|
}
|
|
|
|
LogRuntimeTrace(
|
|
"window",
|
|
"failed to commit window projection for '" + std::string(window.GetWindowId()) +
|
|
"': " + error);
|
|
return;
|
|
}
|
|
|
|
RefreshWindowTitle(window);
|
|
}
|
|
|
|
void EditorWindowWorkspaceCoordinator::HandleWindowDestroyed(const EditorWindow& window) {
|
|
m_workspaceStore.RemoveWindow(window.GetWindowId(), window.IsPrimary());
|
|
}
|
|
|
|
UIEditorWindowWorkspaceController
|
|
EditorWindowWorkspaceCoordinator::BuildWorkspaceMutationController() const {
|
|
return m_workspaceStore.BuildMutationController();
|
|
}
|
|
|
|
UIEditorWorkspaceController EditorWindowWorkspaceCoordinator::BuildWorkspaceControllerForWindow(
|
|
const UIEditorWindowWorkspaceState& windowState) const {
|
|
return UIEditorWorkspaceController(
|
|
m_workspaceStore.GetPanelRegistry(),
|
|
windowState.workspace,
|
|
windowState.session);
|
|
}
|
|
|
|
void EditorWindowWorkspaceCoordinator::RefreshWindowTitle(EditorWindow& window) const {
|
|
if (window.IsPrimary()) {
|
|
return;
|
|
}
|
|
|
|
const std::wstring title = BuildWindowTitle(window.GetWorkspaceController());
|
|
if (title == window.GetTitle()) {
|
|
return;
|
|
}
|
|
|
|
window.SetTitle(title);
|
|
if (window.GetHwnd() != nullptr) {
|
|
SetWindowTextW(window.GetHwnd(), window.GetTitle().c_str());
|
|
}
|
|
}
|
|
|
|
void EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests(
|
|
EditorWindow& sourceWindow,
|
|
EditorWindowFrameTransferRequests&& transferRequests) {
|
|
if (!m_globalTabDragSession.active &&
|
|
transferRequests.beginGlobalTabDrag.has_value() &&
|
|
transferRequests.beginGlobalTabDrag->IsValid()) {
|
|
TryStartGlobalTabDrag(sourceWindow, *transferRequests.beginGlobalTabDrag);
|
|
}
|
|
|
|
if (!m_globalTabDragSession.active &&
|
|
transferRequests.detachPanel.has_value() &&
|
|
transferRequests.detachPanel->IsValid()) {
|
|
TryProcessDetachRequest(sourceWindow, *transferRequests.detachPanel);
|
|
}
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::App::Internal
|