#include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" #include "Bootstrap/EditorResources.h" #include "Composition/EditorContext.h" #include "Platform/Win32/Chrome/EditorWindowChromeController.h" #include "Platform/Win32/Windowing/EditorFloatingWindowPlacement.h" #include "Platform/Win32/Windowing/EditorWindow.h" #include "Windowing/Content/EditorWindowContentFactory.h" #include "Windowing/Content/EditorWindowContentController.h" #include "Platform/Win32/Runtime/EditorWindowFrameDriver.h" #include "Windowing/Coordinator/EditorUtilityWindowCoordinator.h" #include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" #include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h" #include "Windowing/System/EditorWindowSystem.h" #include #include #include #include #include namespace XCEngine::UI::Editor::App { namespace { std::string DescribeHwnd(HWND hwnd) { std::ostringstream stream = {}; stream << "0x" << std::hex << std::uppercase << reinterpret_cast(hwnd); return stream.str(); } std::string DescribeHostWindows( const std::vector>& windows) { std::ostringstream stream = {}; stream << "count=" << windows.size() << " ["; bool first = true; for (const std::unique_ptr& window : windows) { if (!first) { stream << ", "; } first = false; if (window == nullptr) { stream << ""; continue; } stream << window->GetWindowId() << "{hwnd=" << DescribeHwnd(window->GetHwnd()) << ",primary=" << (window->IsPrimary() ? '1' : '0') << ",state=" << GetEditorWindowLifecycleStateName(window->GetLifecycleState()) << '}'; } stream << ']'; return stream.str(); } } // namespace EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, std::filesystem::path repoRoot, EditorContext& editorContext, EditorWindowSystem& windowSystem, EditorWindowContentFactory& contentFactory) : m_hostConfig(hostConfig), m_repoRoot(std::move(repoRoot)), m_editorContext(editorContext), m_windowSystem(windowSystem), m_contentFactory(contentFactory) {} EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( std::unique_ptr contentController, const EditorWindowCreateParams& params) { if (contentController == nullptr) { LogRuntimeTrace("window", "window creation failed: content controller is null"); return nullptr; } const EditorWindowContentCapabilities capabilities = contentController->GetCapabilities(); if (params.category == EditorWindowCategory::Workspace && !capabilities.workspace) { LogRuntimeTrace("window", "workspace window creation rejected: content is not workspace"); return nullptr; } if (params.category == EditorWindowCategory::Utility && !capabilities.utilityPanel) { LogRuntimeTrace("window", "utility window creation rejected: content is not utility"); return nullptr; } auto windowPtr = std::make_unique( params.windowId, params.title.empty() ? std::wstring(L"XCEngine Editor") : params.title, params.category, params.chromePolicy, params.primary, std::move(contentController)); 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& candidate) { return candidate.get() == rawWindow; }); if (it != m_windows.end()) { m_windows.erase(it); } }; m_pendingCreateWindow = rawWindow; const DWORD windowStyle = params.nativeStylePolicy.useHostWindowStyle ? m_hostConfig.windowStyle : static_cast(params.nativeStylePolicy.windowStyle); const DWORD extendedWindowStyle = params.nativeStylePolicy.extendedWindowStyle != 0u ? static_cast(params.nativeStylePolicy.extendedWindowStyle) : WS_EX_APPWINDOW; const int initialX = params.initialX == kEditorWindowDefaultPosition ? CW_USEDEFAULT : params.initialX; const int initialY = params.initialY == kEditorWindowDefaultPosition ? CW_USEDEFAULT : params.initialY; const HWND hwnd = CreateWindowExW( extendedWindowStyle, m_hostConfig.windowClassName, rawWindow->GetTitle().c_str(), windowStyle, initialX, 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)); m_lifecycleCoordinator->AbortUnregisteredWindow(*rawWindow); return static_cast(nullptr); }; const HICON bigIcon = static_cast( LoadImageW( m_hostConfig.hInstance, MAKEINTRESOURCEW(IDI_APP_ICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE)); const HICON smallIcon = static_cast( 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(bigIcon)); } if (smallIcon != nullptr) { SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(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; } EditorWindow* EditorWindowHostRuntime::CreateWorkspaceWindow( UIEditorWorkspaceController workspaceController, const EditorWindowCreateParams& params) { return CreateEditorWindow( m_contentFactory.CreateWorkspaceContentController( params.windowId, std::move(workspaceController), m_windowSystem), params); } EditorWindow* EditorWindowHostRuntime::CreateUtilityWindow( const EditorUtilityWindowDescriptor& descriptor, const EditorWindowCreateParams& params) { return CreateEditorWindow( m_contentFactory.CreateUtilityContentController(descriptor), params); } void EditorWindowHostRuntime::BindLifecycleCoordinator( EditorWindowLifecycleCoordinator& lifecycleCoordinator) { m_lifecycleCoordinator = &lifecycleCoordinator; } void EditorWindowHostRuntime::HandlePendingNativeWindowCreated(HWND hwnd) { if (m_pendingCreateWindow != nullptr && m_pendingCreateWindow->GetLifecycleState() == EditorWindowLifecycleState::PendingNativeCreate && !m_pendingCreateWindow->HasHwnd()) { m_pendingCreateWindow->AttachHwnd(hwnd); } } bool EditorWindowHostRuntime::HasWindows() const { return !m_windows.empty(); } std::vector EditorWindowHostRuntime::GetWindows() { std::vector windows = {}; windows.reserve(m_windows.size()); for (const std::unique_ptr& window : m_windows) { if (window != nullptr) { windows.push_back(window.get()); } } return windows; } std::vector EditorWindowHostRuntime::GetWindows() const { std::vector windows = {}; windows.reserve(m_windows.size()); for (const std::unique_ptr& window : m_windows) { if (window != nullptr) { windows.push_back(window.get()); } } return windows; } std::wstring_view EditorWindowHostRuntime::GetPrimaryWindowTitle() const { return m_hostConfig.primaryWindowTitle != nullptr ? std::wstring_view(m_hostConfig.primaryWindowTitle) : std::wstring_view{}; } bool EditorWindowHostRuntime::TryGetCursorScreenPoint( EditorWindowScreenPoint& outPoint) const { POINT nativePoint = {}; if (!GetCursorPos(&nativePoint)) { outPoint = {}; return false; } outPoint.x = nativePoint.x; outPoint.y = nativePoint.y; return true; } EditorWindowScreenRect EditorWindowHostRuntime::ResolveFloatingPlacement( const EditorWindowScreenPoint& screenPoint, int preferredWidth, int preferredHeight) const { POINT nativePoint = {}; nativePoint.x = screenPoint.x; nativePoint.y = screenPoint.y; const RECT nativeRect = BuildEditorFloatingWindowRect( nativePoint, preferredWidth, preferredHeight); EditorWindowScreenRect rect = {}; rect.left = nativeRect.left; rect.top = nativeRect.top; rect.right = nativeRect.right; rect.bottom = nativeRect.bottom; return rect; } EditorHostWindow* EditorWindowHostRuntime::FindWindowFromScreenPoint( const EditorWindowScreenPoint& screenPoint) { POINT nativePoint = {}; nativePoint.x = screenPoint.x; nativePoint.y = screenPoint.y; const HWND hitWindow = WindowFromPoint(nativePoint); if (hitWindow == nullptr) { return nullptr; } return FindWindow(GetAncestor(hitWindow, GA_ROOT)); } const EditorHostWindow* EditorWindowHostRuntime::FindWindowFromScreenPoint( const EditorWindowScreenPoint& screenPoint) const { return const_cast(this)->FindWindowFromScreenPoint(screenPoint); } void EditorWindowHostRuntime::ReapDestroyedWindows() { for (auto it = m_windows.begin(); it != m_windows.end();) { EditorWindow* const window = it->get(); if (window == nullptr || !window->IsDestroyed()) { ++it; continue; } if (m_pendingCreateWindow == window) { m_pendingCreateWindow = nullptr; } LogRuntimeTrace( "window-close", "ReapDestroyedWindows erase windowId='" + std::string(window->GetWindowId()) + "' hostBefore=" + DescribeHostWindows(m_windows)); it = m_windows.erase(it); LogRuntimeTrace( "window-close", "ReapDestroyedWindows erase end hostAfter=" + DescribeHostWindows(m_windows)); } } std::string EditorWindowHostRuntime::DescribeWindows() const { return DescribeHostWindows(m_windows); } void EditorWindowHostRuntime::RenderAllWindows( bool globalTabDragActive, EditorWindowWorkspaceCoordinator& workspaceCoordinator, EditorUtilityWindowCoordinator& utilityCoordinator) { struct WindowFrameTransferBatch { EditorWindow* sourceWindow = nullptr; EditorWindowFrameTransferRequests requests = {}; }; std::vector transferBatches = {}; transferBatches.reserve(m_windows.size()); for (const std::unique_ptr& window : m_windows) { if (window == nullptr || !window->HasLiveHostWindow() || window->GetLifecycleState() != EditorWindowLifecycleState::Running) { continue; } if (window->ConsumeSkipNextSteadyStateFrame()) { workspaceCoordinator.RefreshWindowPresentation(*window); continue; } EditorWindowFrameTransferRequests transferRequests = EditorWindowFrameDriver::DriveFrame( *window, m_editorContext, globalTabDragActive); workspaceCoordinator.RefreshWindowPresentation(*window); if (!transferRequests.HasPendingRequests()) { continue; } transferBatches.push_back(WindowFrameTransferBatch{ window.get(), std::move(transferRequests), }); } for (WindowFrameTransferBatch& batch : transferBatches) { if (batch.sourceWindow == nullptr || !batch.sourceWindow->HasLiveHostWindow() || batch.sourceWindow->GetLifecycleState() != EditorWindowLifecycleState::Running) { continue; } workspaceCoordinator.HandleWindowFrameTransferRequests( *batch.sourceWindow, batch.requests); utilityCoordinator.HandleWindowFrameTransferRequests( *batch.sourceWindow, batch.requests); } } EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) { if (hwnd == nullptr) { return nullptr; } for (const std::unique_ptr& window : m_windows) { if (window != nullptr && window->GetHwnd() == hwnd) { return window.get(); } } return nullptr; } const EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) const { return const_cast(this)->FindWindow(hwnd); } EditorWindow* EditorWindowHostRuntime::FindWindowByIdImpl(std::string_view windowId) { if (windowId.empty()) { return nullptr; } for (const std::unique_ptr& window : m_windows) { if (window != nullptr && window->GetWindowId() == windowId) { return window.get(); } } return nullptr; } const EditorWindow* EditorWindowHostRuntime::FindWindowByIdImpl(std::string_view windowId) const { return const_cast(this)->FindWindowByIdImpl(windowId); } EditorHostWindow* EditorWindowHostRuntime::FindWindowById(std::string_view windowId) { return FindWindowByIdImpl(windowId); } const EditorHostWindow* EditorWindowHostRuntime::FindWindowById(std::string_view windowId) const { return FindWindowByIdImpl(windowId); } EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() { for (const std::unique_ptr& window : m_windows) { if (window != nullptr && window->IsPrimary()) { return window.get(); } } return nullptr; } const EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() const { return const_cast(this)->FindPrimaryWindow(); } void EditorWindowHostRuntime::LogRuntimeTrace( std::string_view channel, std::string_view message) const { AppendUIEditorRuntimeTrace(channel, message); } } // namespace XCEngine::UI::Editor::App