#include "Windowing/EditorWindowHostRuntime.h" #include "EditorResources.h" #include "Chrome/EditorWindowChromeController.h" #include "Windowing/EditorFloatingWindowPlacement.h" #include "Windowing/EditorWindow.h" #include "Interfaces/EditorWindowHostCoordinator.h" #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->GetOwner().GetWindowId() << "{hwnd=" << DescribeHwnd(window->GetHwnd()) << ",primary=" << (window->GetOwner().IsPrimary() ? '1' : '0') << ",state=" << GetEditorWindowLifecycleStateName( window->GetOwner().GetLifecycleState()) << '}'; } stream << ']'; return stream.str(); } DWORD ResolveExtendedWindowStyle(const EditorWindowNativeHostPolicy& policy) { switch (policy.shellRole) { case EditorWindowNativeShellRole::ToolWindow: return WS_EX_TOOLWINDOW; case EditorWindowNativeShellRole::AppWindow: default: return WS_EX_APPWINDOW; } } } // namespace EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, std::filesystem::path repoRoot, std::filesystem::path captureRoot) : m_hostConfig(hostConfig), m_repoRoot(std::move(repoRoot)), m_captureRoot(std::move(captureRoot)) {} EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; bool EditorWindowHostRuntime::CreateHostWindow( EditorHostWindow& window, const EditorWindowCreateParams& params) { if (params.category == EditorWindowCategory::Workspace && !window.IsWorkspaceWindow()) { LogRuntimeTrace("window", "workspace window creation rejected: content is not workspace"); return false; } if (params.category == EditorWindowCategory::Utility && !window.IsUtilityWindow()) { LogRuntimeTrace("window", "utility window creation rejected: content is not utility"); return false; } auto windowPtr = std::make_unique(window); EditorWindow* const rawWindow = windowPtr.get(); window.AttachNativePeer(*rawWindow); 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 = m_hostConfig.windowStyle; const DWORD extendedWindowStyle = ResolveExtendedWindowStyle(params.nativeHostPolicy); 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().empty() ? L"XCEngine Editor" : 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) { rawWindow->GetOwner().DetachNativePeer(*rawWindow); eraseRawWindow(); return false; } if (!rawWindow->HasHwnd()) { rawWindow->AttachHwnd(hwnd); } auto failWindowInitialization = [&](std::string_view message) { LogRuntimeTrace("window", std::string(message)); if (m_hostCoordinator != nullptr) { m_hostCoordinator->AbortUnregisteredWindow(window); } else { rawWindow->GetOwner().DetachNativePeer(*rawWindow); eraseRawWindow(); } return false; }; 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 (m_hostCoordinator == nullptr) { return failWindowInitialization("managed window initialization failed: coordinator missing"); } if (!m_hostCoordinator->InitializeHostWindow( window, EditorHostWindowRuntimeInitializationParams{ .repoRoot = m_repoRoot, .captureRoot = m_captureRoot, .autoCaptureOnStartup = params.autoCaptureOnStartup, })) { return failWindowInitialization("managed window initialization failed"); } ShowWindow(hwnd, params.showCommand); UpdateWindow(hwnd); return true; } void EditorWindowHostRuntime::BindHostCoordinator( EditorWindowHostCoordinator& hostCoordinator) { m_hostCoordinator = &hostCoordinator; } 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->GetOwner()); } } 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->GetOwner()); } } 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; } EditorWindow* const nativeWindow = FindWindow(GetAncestor(hitWindow, GA_ROOT)); return nativeWindow != nullptr ? &nativeWindow->GetOwner() : nullptr; } 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->GetOwner().IsDestroyed()) { ++it; continue; } if (m_pendingCreateWindow == window) { m_pendingCreateWindow = nullptr; } LogRuntimeTrace( "window-close", "ReapDestroyedWindows erase windowId='" + std::string(window->GetOwner().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); } 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->GetOwner().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) { EditorWindow* const window = FindWindowByIdImpl(windowId); return window != nullptr ? &window->GetOwner() : nullptr; } const EditorHostWindow* EditorWindowHostRuntime::FindWindowById(std::string_view windowId) const { const EditorWindow* const window = FindWindowByIdImpl(windowId); return window != nullptr ? &window->GetOwner() : nullptr; } EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() { for (const std::unique_ptr& window : m_windows) { if (window != nullptr && window->GetOwner().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