#include "Platform/Win32/EditorWindow.h" #include "Bootstrap/EditorResources.h" #include "Platform/Win32/EditorWindowConstants.h" #include "Platform/Win32/EditorWindowPlatformInternal.h" #include "Platform/Win32/EditorWindowRuntimeInternal.h" #include "State/EditorContext.h" #include #include #include #include namespace XCEngine::UI::Editor::App::EditorWindowInternal { UINT QuerySystemDpi() { HDC screenDc = GetDC(nullptr); if (screenDc == nullptr) { return kDefaultDpi; } const int dpiX = GetDeviceCaps(screenDc, LOGPIXELSX); ReleaseDC(nullptr, screenDc); return dpiX > 0 ? static_cast(dpiX) : kDefaultDpi; } UINT QueryWindowDpi(HWND hwnd) { if (hwnd != nullptr) { const HMODULE user32 = GetModuleHandleW(L"user32.dll"); if (user32 != nullptr) { using GetDpiForWindowFn = UINT(WINAPI*)(HWND); const auto getDpiForWindow = reinterpret_cast(GetProcAddress(user32, "GetDpiForWindow")); if (getDpiForWindow != nullptr) { const UINT dpi = getDpiForWindow(hwnd); if (dpi != 0u) { return dpi; } } } } return QuerySystemDpi(); } bool ResolveVerboseRuntimeTraceEnabled() { wchar_t buffer[8] = {}; const DWORD length = GetEnvironmentVariableW( L"XCUIEDITOR_VERBOSE_TRACE", buffer, static_cast(std::size(buffer))); return length > 0u && buffer[0] != L'0'; } void LogRuntimeTrace(std::string_view channel, std::string_view message) { AppendUIEditorRuntimeTrace(channel, message); } bool IsAutoCaptureOnStartupEnabled() { return App::Internal::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP"); } } // namespace XCEngine::UI::Editor::App::EditorWindowInternal namespace XCEngine::UI::Editor::App { using namespace EditorWindowInternal; using ::XCEngine::UI::UIPoint; EditorWindow::EditorWindow( std::string windowId, std::wstring title, bool primary, UIEditorWorkspaceController workspaceController) : m_window{ nullptr, std::move(windowId), std::move(title), {}, primary, false } , m_composition{ std::move(workspaceController), {} } { UpdateCachedTitleText(); } std::string_view EditorWindow::GetWindowId() const { return m_window.windowId; } HWND EditorWindow::GetHwnd() const { return m_window.hwnd; } bool EditorWindow::HasHwnd() const { return m_window.hwnd != nullptr; } bool EditorWindow::IsPrimary() const { return m_window.primary; } bool EditorWindow::IsClosing() const { return m_window.closing; } bool EditorWindow::IsRenderReady() const { return m_render.ready; } bool EditorWindow::IsTrackingMouseLeave() const { return m_input.trackingMouseLeave; } bool EditorWindow::HasHoveredBorderlessResizeEdge() const { return m_chrome.runtime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None; } const std::wstring& EditorWindow::GetTitle() const { return m_window.title; } const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const { return m_composition.workspaceController; } UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() { return m_composition.workspaceController; } const EditorShellRuntime& EditorWindow::GetShellRuntime() const { return m_composition.shellRuntime; } EditorShellRuntime& EditorWindow::GetShellRuntime() { return m_composition.shellRuntime; } const UIEditorShellInteractionFrame& EditorWindow::GetShellFrame() const { return m_composition.shellRuntime.GetShellFrame(); } const UIEditorShellInteractionState& EditorWindow::GetShellInteractionState() const { return m_composition.shellRuntime.GetShellInteractionState(); } void EditorWindow::SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview) { m_composition.shellRuntime.SetExternalDockHostDropPreview(preview); } void EditorWindow::ClearExternalDockHostDropPreview() { m_composition.shellRuntime.ClearExternalDockHostDropPreview(); } void EditorWindow::AttachHwnd(HWND hwnd) { m_window.hwnd = hwnd; m_window.closing = false; } void EditorWindow::MarkDestroyed() { m_window.hwnd = nullptr; m_window.closing = false; m_input.trackingMouseLeave = false; } void EditorWindow::MarkClosing() { m_window.closing = true; } void EditorWindow::ClearClosing() { m_window.closing = false; } void EditorWindow::SetTrackingMouseLeave(bool trackingMouseLeave) { m_input.trackingMouseLeave = trackingMouseLeave; } void EditorWindow::SetTitle(std::wstring title) { m_window.title = std::move(title); UpdateCachedTitleText(); } void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) { m_composition.workspaceController = std::move(workspaceController); } void EditorWindow::InvalidateHostWindow() const { if (m_window.hwnd != nullptr && IsWindow(m_window.hwnd)) { InvalidateRect(m_window.hwnd, nullptr, FALSE); } } bool EditorWindow::Initialize( const std::filesystem::path& repoRoot, EditorContext& editorContext, const std::filesystem::path& captureRoot, bool autoCaptureOnStartup) { if (m_window.hwnd == nullptr) { LogRuntimeTrace("app", "window initialize skipped: hwnd is null"); return false; } Host::RefreshBorderlessWindowDwmDecorations(m_window.hwnd); m_chrome.runtime.Reset(); m_chrome.runtime.SetWindowDpi(QueryWindowDpi(m_window.hwnd)); m_render.renderer.SetDpiScale(GetDpiScale()); std::ostringstream dpiTrace = {}; dpiTrace << "initial dpi=" << m_chrome.runtime.GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", dpiTrace.str()); if (!m_render.renderer.Initialize(m_window.hwnd)) { LogRuntimeTrace("app", "renderer initialization failed"); return false; } RECT clientRect = {}; GetClientRect(m_window.hwnd, &clientRect); const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L); const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L); if (!m_render.windowRenderer.Initialize(m_window.hwnd, clientWidth, clientHeight)) { LogRuntimeTrace("app", "d3d12 window renderer initialization failed"); m_render.renderer.Shutdown(); return false; } const Host::D3D12WindowRenderLoopAttachResult attachResult = m_render.windowRenderLoop.Attach(m_render.renderer, m_render.windowRenderer); if (!attachResult.interopWarning.empty()) { LogRuntimeTrace("app", attachResult.interopWarning); } editorContext.AttachTextMeasurer(m_render.renderer); m_composition.shellRuntime.Initialize(repoRoot, m_render.renderer); m_composition.shellRuntime.AttachViewportWindowRenderer(m_render.windowRenderer); m_composition.shellRuntime.SetViewportSurfacePresentationEnabled( attachResult.hasViewportSurfacePresentation); std::string titleBarLogoError = {}; if (!LoadEmbeddedPngTexture( m_render.renderer, IDR_PNG_LOGO_ICON, m_render.titleBarLogoIcon, titleBarLogoError)) { LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError); } if (!m_composition.shellRuntime.GetBuiltInIconError().empty()) { LogRuntimeTrace("icons", m_composition.shellRuntime.GetBuiltInIconError()); } LogRuntimeTrace( "app", "shell runtime initialized: " + editorContext.DescribeWorkspaceState( m_composition.workspaceController, m_composition.shellRuntime.GetShellInteractionState())); m_render.ready = true; m_render.autoScreenshot.Initialize(captureRoot); if (autoCaptureOnStartup && IsAutoCaptureOnStartupEnabled()) { m_render.autoScreenshot.RequestCapture("startup"); editorContext.SetStatus("Capture", "Startup capture requested."); } return true; } void EditorWindow::Shutdown() { ForceReleasePointerCapture(); m_render.ready = false; m_render.autoScreenshot.Shutdown(); m_composition.shellRuntime.Shutdown(); m_render.renderer.ReleaseTexture(m_render.titleBarLogoIcon); m_render.windowRenderLoop.Detach(); m_render.windowRenderer.Shutdown(); m_render.renderer.Shutdown(); m_input.pendingEvents.clear(); m_chrome.chromeState = {}; m_chrome.runtime.Reset(); } void EditorWindow::ResetInteractionState() { ForceReleasePointerCapture(); m_input.pendingEvents.clear(); m_input.trackingMouseLeave = false; m_input.modifierTracker.Reset(); m_composition.shellRuntime.ResetInteractionState(); m_chrome.chromeState = {}; m_chrome.runtime.EndBorderlessResize(); m_chrome.runtime.EndBorderlessWindowDragRestore(); m_chrome.runtime.EndInteractiveResize(); m_chrome.runtime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); m_chrome.runtime.ClearPredictedClientPixelSize(); } bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { if (!m_render.ready || width == 0u || height == 0u) { return false; } const Host::D3D12WindowRenderLoopResizeResult resizeResult = m_render.windowRenderLoop.ApplyResize(width, height); m_composition.shellRuntime.SetViewportSurfacePresentationEnabled( resizeResult.hasViewportSurfacePresentation); if (!resizeResult.windowRendererWarning.empty()) { LogRuntimeTrace("present", resizeResult.windowRendererWarning); } if (!resizeResult.interopWarning.empty()) { LogRuntimeTrace("present", resizeResult.interopWarning); } return resizeResult.hasViewportSurfacePresentation; } bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const { outWidth = 0u; outHeight = 0u; if (m_window.hwnd == nullptr || !IsWindow(m_window.hwnd)) { return false; } RECT clientRect = {}; if (!GetClientRect(m_window.hwnd, &clientRect)) { return false; } const LONG width = clientRect.right - clientRect.left; const LONG height = clientRect.bottom - clientRect.top; if (width <= 0 || height <= 0) { return false; } outWidth = static_cast(width); outHeight = static_cast(height); return true; } bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const { if (m_chrome.runtime.TryGetPredictedClientPixelSize(outWidth, outHeight)) { return true; } return QueryCurrentClientPixelSize(outWidth, outHeight); } float EditorWindow::GetDpiScale() const { return m_chrome.runtime.GetDpiScale(kBaseDpiScale); } float EditorWindow::PixelsToDips(float pixels) const { const float dpiScale = GetDpiScale(); return dpiScale > 0.0f ? pixels / dpiScale : pixels; } UIPoint EditorWindow::ConvertClientPixelsToDips(LONG x, LONG y) const { return UIPoint( PixelsToDips(static_cast(x)), PixelsToDips(static_cast(y))); } UIPoint EditorWindow::ConvertScreenPixelsToClientDips(const POINT& screenPoint) const { POINT clientPoint = screenPoint; if (m_window.hwnd != nullptr) { ScreenToClient(m_window.hwnd, &clientPoint); } const float dpiScale = m_chrome.runtime.GetDpiScale(kBaseDpiScale); return UIPoint( dpiScale > 0.0f ? static_cast(clientPoint.x) / dpiScale : static_cast(clientPoint.x), dpiScale > 0.0f ? static_cast(clientPoint.y) / dpiScale : static_cast(clientPoint.y)); } void EditorWindow::OnResize(UINT width, UINT height) { bool matchesPredictedClientSize = false; UINT predictedWidth = 0u; UINT predictedHeight = 0u; if (m_chrome.runtime.TryGetPredictedClientPixelSize(predictedWidth, predictedHeight)) { matchesPredictedClientSize = predictedWidth == width && predictedHeight == height; } m_chrome.runtime.ClearPredictedClientPixelSize(); if (IsBorderlessWindowEnabled() && m_window.hwnd != nullptr) { Host::RefreshBorderlessWindowDwmDecorations(m_window.hwnd); } if (!matchesPredictedClientSize) { ApplyWindowResize(width, height); } } void EditorWindow::OnEnterSizeMove() { m_chrome.runtime.BeginInteractiveResize(); } void EditorWindow::OnExitSizeMove() { m_chrome.runtime.EndInteractiveResize(); m_chrome.runtime.ClearPredictedClientPixelSize(); UINT width = 0u; UINT height = 0u; if (QueryCurrentClientPixelSize(width, height)) { ApplyWindowResize(width, height); } } void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { m_chrome.runtime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); m_render.renderer.SetDpiScale(GetDpiScale()); if (m_window.hwnd != nullptr) { const LONG windowWidth = suggestedRect.right - suggestedRect.left; const LONG windowHeight = suggestedRect.bottom - suggestedRect.top; SetWindowPos( m_window.hwnd, nullptr, suggestedRect.left, suggestedRect.top, windowWidth, windowHeight, SWP_NOZORDER | SWP_NOACTIVATE); UINT clientWidth = 0u; UINT clientHeight = 0u; if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) { ApplyWindowResize(clientWidth, clientHeight); } Host::RefreshBorderlessWindowDwmDecorations(m_window.hwnd); } std::ostringstream trace = {}; trace << "dpi changed to " << m_chrome.runtime.GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", trace.str()); } bool EditorWindow::IsVerboseRuntimeTraceEnabled() { static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled(); return s_enabled; } void EditorWindow::UpdateCachedTitleText() { m_window.titleText = WideToUtf8(m_window.title); } } // namespace XCEngine::UI::Editor::App