#include "Platform/Win32/EditorWindow.h" #include "Bootstrap/EditorResources.h" #include "Platform/Win32/EditorWindowChromeController.h" #include "Platform/Win32/EditorWindowConstants.h" #include "Platform/Win32/EditorWindowFrameOrchestrator.h" #include "Platform/Win32/EditorWindowInputController.h" #include "Platform/Win32/EditorWindowInternalState.h" #include "Platform/Win32/EditorWindowPlatformInternal.h" #include "Platform/Win32/EditorWindowRuntimeController.h" #include "Platform/Win32/EditorWindowRuntimeInternal.h" #include "Composition/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_state(std::make_unique()) , m_chromeController(std::make_unique()) , m_frameOrchestrator(std::make_unique()) , m_inputController(std::make_unique()) , m_runtime(std::make_unique( std::move(workspaceController))) { m_state->window.windowId = std::move(windowId); m_state->window.title = std::move(title); m_state->window.primary = primary; UpdateCachedTitleText(); } EditorWindow::~EditorWindow() = default; std::string_view EditorWindow::GetWindowId() const { return m_state->window.windowId; } HWND EditorWindow::GetHwnd() const { return m_state->window.hwnd; } bool EditorWindow::HasHwnd() const { return m_state->window.hwnd != nullptr; } bool EditorWindow::IsPrimary() const { return m_state->window.primary; } bool EditorWindow::IsClosing() const { return m_state->window.closing; } bool EditorWindow::IsRenderReady() const { return m_runtime->IsReady(); } bool EditorWindow::IsTrackingMouseLeave() const { return m_inputController->IsTrackingMouseLeave(); } bool EditorWindow::HasHoveredBorderlessResizeEdge() const { return m_chromeController->GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None; } const std::wstring& EditorWindow::GetTitle() const { return m_state->window.title; } const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const { return m_runtime->GetWorkspaceController(); } UIEditorWorkspaceController& EditorWindow::GetMutableWorkspaceController() { return m_runtime->GetMutableWorkspaceController(); } const EditorShellRuntime& EditorWindow::GetShellRuntime() const { return m_runtime->GetShellRuntime(); } EditorShellRuntime& EditorWindow::GetShellRuntime() { return m_runtime->GetShellRuntime(); } const UIEditorShellInteractionFrame& EditorWindow::GetShellFrame() const { return m_runtime->GetShellFrame(); } const UIEditorShellInteractionState& EditorWindow::GetShellInteractionState() const { return m_runtime->GetShellInteractionState(); } void EditorWindow::SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview) { m_runtime->SetExternalDockHostDropPreview(preview); } void EditorWindow::ClearExternalDockHostDropPreview() { m_runtime->ClearExternalDockHostDropPreview(); } void EditorWindow::AttachHwnd(HWND hwnd) { m_state->window.hwnd = hwnd; m_state->window.closing = false; } void EditorWindow::MarkDestroyed() { m_state->window.hwnd = nullptr; m_state->window.closing = false; m_inputController->ResetWindowState(); } void EditorWindow::MarkClosing() { m_state->window.closing = true; } void EditorWindow::ClearClosing() { m_state->window.closing = false; } void EditorWindow::SetTrackingMouseLeave(bool trackingMouseLeave) { m_inputController->SetTrackingMouseLeave(trackingMouseLeave); } void EditorWindow::SetTitle(std::wstring title) { m_state->window.title = std::move(title); UpdateCachedTitleText(); } void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) { m_runtime->ReplaceWorkspaceController(std::move(workspaceController)); } void EditorWindow::InvalidateHostWindow() const { if (m_state->window.hwnd != nullptr && IsWindow(m_state->window.hwnd)) { InvalidateRect(m_state->window.hwnd, nullptr, FALSE); } } bool EditorWindow::Initialize( const std::filesystem::path& repoRoot, EditorContext& editorContext, const std::filesystem::path& captureRoot, bool autoCaptureOnStartup) { if (m_state->window.hwnd == nullptr) { LogRuntimeTrace("app", "window initialize skipped: hwnd is null"); return false; } Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd); m_chromeController->Reset(); m_chromeController->SetWindowDpi(QueryWindowDpi(m_state->window.hwnd)); m_runtime->SetDpiScale(GetDpiScale()); std::ostringstream dpiTrace = {}; dpiTrace << "initial dpi=" << m_chromeController->GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", dpiTrace.str()); return m_runtime->Initialize( m_state->window.hwnd, repoRoot, editorContext, captureRoot, autoCaptureOnStartup); } void EditorWindow::Shutdown() { ForceReleasePointerCapture(); m_runtime->Shutdown(); m_inputController->ClearPendingEvents(); m_chromeController->Reset(); } void EditorWindow::ResetInteractionState() { ForceReleasePointerCapture(); m_inputController->ResetInteractionState(); m_runtime->ResetInteractionState(); m_chromeController->ResetChromeState(); m_chromeController->EndBorderlessResize(); m_chromeController->EndBorderlessWindowDragRestore(); m_chromeController->EndInteractiveResize(); m_chromeController->SetHoveredBorderlessResizeEdge( Host::BorderlessWindowResizeEdge::None); m_chromeController->ClearPredictedClientPixelSize(); } bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { if (!m_runtime->IsReady() || width == 0u || height == 0u) { return false; } return m_runtime->ApplyResize(width, height); } bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const { outWidth = 0u; outHeight = 0u; if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) { return false; } RECT clientRect = {}; if (!GetClientRect(m_state->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_chromeController->TryGetPredictedClientPixelSize(outWidth, outHeight)) { return true; } return QueryCurrentClientPixelSize(outWidth, outHeight); } float EditorWindow::GetDpiScale() const { return m_chromeController->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_state->window.hwnd != nullptr) { ScreenToClient(m_state->window.hwnd, &clientPoint); } const float dpiScale = m_chromeController->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_chromeController->TryGetPredictedClientPixelSize( predictedWidth, predictedHeight)) { matchesPredictedClientSize = predictedWidth == width && predictedHeight == height; } m_chromeController->ClearPredictedClientPixelSize(); if (IsBorderlessWindowEnabled() && m_state->window.hwnd != nullptr) { Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd); } if (!matchesPredictedClientSize) { ApplyWindowResize(width, height); } } void EditorWindow::OnEnterSizeMove() { m_chromeController->BeginInteractiveResize(); } void EditorWindow::OnExitSizeMove() { m_chromeController->EndInteractiveResize(); m_chromeController->ClearPredictedClientPixelSize(); UINT width = 0u; UINT height = 0u; if (QueryCurrentClientPixelSize(width, height)) { ApplyWindowResize(width, height); } } void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { m_chromeController->SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); m_runtime->SetDpiScale(GetDpiScale()); if (m_state->window.hwnd != nullptr) { const LONG windowWidth = suggestedRect.right - suggestedRect.left; const LONG windowHeight = suggestedRect.bottom - suggestedRect.top; SetWindowPos( m_state->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_state->window.hwnd); } std::ostringstream trace = {}; trace << "dpi changed to " << m_chromeController->GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", trace.str()); } bool EditorWindow::IsVerboseRuntimeTraceEnabled() { static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled(); return s_enabled; } void EditorWindow::UpdateCachedTitleText() { m_state->window.titleText = WideToUtf8(m_state->window.title); } } // namespace XCEngine::UI::Editor::App