#include "Platform/Win32/WindowManager/Internal.h" #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" #include #include #include namespace XCEngine::UI::Editor::App::Internal { namespace { struct ExistingWindowSnapshot { EditorWindow* window = nullptr; UIEditorWorkspaceController workspaceController = {}; std::wstring title = {}; }; } // namespace std::wstring EditorWindowWorkspaceCoordinator::BuildWindowTitle( const UIEditorWorkspaceController& workspaceController) const { const std::string& activePanelId = workspaceController.GetWorkspace().activePanelId; if (const UIEditorPanelDescriptor* descriptor = FindUIEditorPanelDescriptor( workspaceController.GetPanelRegistry(), activePanelId); descriptor != nullptr && !descriptor->defaultTitle.empty()) { const std::string titleText = descriptor->defaultTitle + " - XCEngine Editor"; return std::wstring(titleText.begin(), titleText.end()); } return std::wstring(L"XCEngine Editor"); } RECT EditorWindowWorkspaceCoordinator::BuildDetachedWindowRect(const POINT& screenPoint) const { RECT rect = { screenPoint.x - 420, screenPoint.y - 24, screenPoint.x - 420 + 960, screenPoint.y - 24 + 720 }; const HMONITOR monitor = MonitorFromPoint(screenPoint, MONITOR_DEFAULTTONEAREST); MONITORINFO monitorInfo = {}; monitorInfo.cbSize = sizeof(monitorInfo); if (monitor != nullptr && GetMonitorInfoW(monitor, &monitorInfo)) { const RECT& workArea = monitorInfo.rcWork; const LONG width = rect.right - rect.left; const LONG height = rect.bottom - rect.top; rect.left = (std::max)(workArea.left, (std::min)(rect.left, workArea.right - width)); rect.top = (std::max)(workArea.top, (std::min)(rect.top, workArea.bottom - height)); rect.right = rect.left + width; rect.bottom = rect.top + height; } return rect; } bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( const UIEditorWindowWorkspaceSet& windowSet, std::string_view preferredNewWindowId, const POINT& preferredScreenPoint) { const auto restoreWindowSnapshot = [](const ExistingWindowSnapshot& snapshot) { if (snapshot.window == nullptr) { return; } snapshot.window->ReplaceWorkspaceController(snapshot.workspaceController); snapshot.window->SetTitle(snapshot.title); snapshot.window->ResetInteractionState(); if (snapshot.window->GetHwnd() != nullptr) { SetWindowTextW(snapshot.window->GetHwnd(), snapshot.window->GetTitle().c_str()); } }; const auto destroyAndEraseWindowById = [this](std::string_view windowId) { auto& windows = m_hostRuntime.GetWindows(); const auto it = std::find_if( windows.begin(), windows.end(), [windowId](const std::unique_ptr& candidate) { return candidate != nullptr && candidate->GetWindowId() == windowId; }); if (it == windows.end() || *it == nullptr) { return false; } EditorWindow& window = *it->get(); const HWND hwnd = window.GetHwnd(); window.ForceReleasePointerCapture(); window.Shutdown(); if (hwnd != nullptr && IsWindow(hwnd)) { DestroyWindow(hwnd); } window.MarkDestroyed(); windows.erase(it); return true; }; std::vector windowIdsInSet = {}; windowIdsInSet.reserve(windowSet.windows.size()); std::vector existingWindowSnapshots = {}; existingWindowSnapshots.reserve(windowSet.windows.size()); std::vector createdWindowIds = {}; for (const UIEditorWindowWorkspaceState& entry : windowSet.windows) { windowIdsInSet.push_back(entry.windowId); if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(entry.windowId); existingWindow != nullptr) { existingWindow->ClearClosing(); existingWindowSnapshots.push_back(ExistingWindowSnapshot{ existingWindow, existingWindow->GetWorkspaceController(), existingWindow->GetTitle(), }); existingWindow->ReplaceWorkspaceController(BuildWorkspaceControllerForWindow(entry)); existingWindow->ResetInteractionState(); if (!existingWindow->IsPrimary()) { existingWindow->SetTitle( BuildWindowTitle(existingWindow->GetWorkspaceController())); if (existingWindow->GetHwnd() != nullptr) { SetWindowTextW(existingWindow->GetHwnd(), existingWindow->GetTitle().c_str()); } } continue; } EditorWindowHostRuntime::CreateParams createParams = {}; createParams.windowId = entry.windowId; createParams.primary = entry.windowId == windowSet.primaryWindowId; createParams.title = createParams.primary ? std::wstring( m_hostRuntime.GetHostConfig().primaryWindowTitle != nullptr && m_hostRuntime.GetHostConfig().primaryWindowTitle[0] != L'\0' ? m_hostRuntime.GetHostConfig().primaryWindowTitle : L"XCEngine Editor") : BuildWindowTitle(BuildWorkspaceControllerForWindow(entry)); if (entry.windowId == preferredNewWindowId) { const RECT detachedRect = BuildDetachedWindowRect(preferredScreenPoint); createParams.initialX = detachedRect.left; createParams.initialY = detachedRect.top; createParams.initialWidth = detachedRect.right - detachedRect.left; createParams.initialHeight = detachedRect.bottom - detachedRect.top; } if (m_hostRuntime.CreateEditorWindow(BuildWorkspaceControllerForWindow(entry), createParams) == nullptr) { for (const ExistingWindowSnapshot& snapshot : existingWindowSnapshots) { restoreWindowSnapshot(snapshot); } for (auto it = createdWindowIds.rbegin(); it != createdWindowIds.rend(); ++it) { destroyAndEraseWindowById(*it); } return false; } createdWindowIds.push_back(entry.windowId); } for (const std::unique_ptr& window : m_hostRuntime.GetWindows()) { if (window == nullptr || window->GetHwnd() == nullptr || window->IsPrimary()) { continue; } const bool existsInWindowSet = std::find( windowIdsInSet.begin(), windowIdsInSet.end(), window->GetWindowId()) != windowIdsInSet.end(); if (!existsInWindowSet) { window->MarkClosing(); PostMessageW(window->GetHwnd(), WM_CLOSE, 0, 0); } } return true; } bool EditorWindowWorkspaceCoordinator::CommitWindowWorkspaceMutation( const UIEditorWindowWorkspaceController& windowWorkspaceController, std::string_view preferredNewWindowId, const POINT& preferredScreenPoint) { const UIEditorWindowWorkspaceSet nextWindowSet = windowWorkspaceController.GetWindowSet(); std::string error = {}; if (!m_workspaceStore.ValidateWindowSet(nextWindowSet, error)) { LogRuntimeTrace("window", "workspace mutation validation failed: " + error); return false; } if (!SynchronizeWindowsFromWindowSet( nextWindowSet, preferredNewWindowId, preferredScreenPoint)) { return false; } m_workspaceStore.ReplaceWindowSet(nextWindowSet); return true; } } // namespace XCEngine::UI::Editor::App::Internal