#include "Bootstrap/Application.h" #include "Bootstrap/EditorResources.h" #include "Ports/SystemInteractionPort.h" #include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindowManager.h" #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/Win32SystemInteractionHost.h" #include "Support/ExecutablePath.h" #include #include #include #include namespace XCEngine::UI::Editor { namespace { constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost"; constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor"; constexpr DWORD kBorderlessWindowStyle = WS_POPUP | WS_THICKFRAME; void EnableDpiAwareness() { const HMODULE user32 = GetModuleHandleW(L"user32.dll"); if (user32 != nullptr) { using SetProcessDpiAwarenessContextFn = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT); const auto setProcessDpiAwarenessContext = reinterpret_cast( GetProcAddress(user32, "SetProcessDpiAwarenessContext")); if (setProcessDpiAwarenessContext != nullptr) { if (setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { return; } if (GetLastError() == ERROR_ACCESS_DENIED) { return; } if (setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) { return; } if (GetLastError() == ERROR_ACCESS_DENIED) { return; } } } const HMODULE shcore = LoadLibraryW(L"shcore.dll"); if (shcore != nullptr) { using SetProcessDpiAwarenessFn = HRESULT(WINAPI*)(PROCESS_DPI_AWARENESS); const auto setProcessDpiAwareness = reinterpret_cast(GetProcAddress(shcore, "SetProcessDpiAwareness")); if (setProcessDpiAwareness != nullptr) { const HRESULT hr = setProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); FreeLibrary(shcore); if (SUCCEEDED(hr) || hr == E_ACCESSDENIED) { return; } } else { FreeLibrary(shcore); } } if (user32 != nullptr) { using SetProcessDPIAwareFn = BOOL(WINAPI*)(); const auto setProcessDPIAware = reinterpret_cast(GetProcAddress(user32, "SetProcessDPIAware")); if (setProcessDPIAware != nullptr) { setProcessDPIAware(); } } } } // namespace Application::Application() : m_editorContext(std::make_unique()) {} Application::~Application() = default; int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow) { Application application; return application.Run(hInstance, nCmdShow); } } // namespace XCEngine::UI::Editor #ifndef XCUIEDITOR_REPO_ROOT #define XCUIEDITOR_REPO_ROOT "." #endif namespace XCEngine::UI::Editor { using App::GetExecutableDirectory; bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { m_hInstance = hInstance; m_repoRoot = ResolveRepoRootPath(); EnableDpiAwareness(); const std::filesystem::path logRoot = GetExecutableDirectory() / "logs"; InitializeUIEditorRuntimeTrace(logRoot); SetUnhandledExceptionFilter(&Application::HandleUnhandledException); AppendUIEditorRuntimeTrace("app", "initialize begin"); if (!m_editorContext->Initialize(m_repoRoot)) { AppendUIEditorRuntimeTrace( "app", "shell asset validation failed: " + m_editorContext->GetValidationMessage()); return false; } if (!RegisterWindowClass()) { return false; } m_systemInteractionHost = std::make_unique(); m_editorContext->SetSystemInteractionHost(m_systemInteractionHost.get()); App::EditorWindowHostConfig hostConfig = {}; hostConfig.hInstance = m_hInstance; hostConfig.windowClassName = kWindowClassName; hostConfig.windowStyle = kBorderlessWindowStyle; hostConfig.primaryWindowTitle = kWindowTitle; hostConfig.windowUserData = this; m_windowManager = std::make_unique( hostConfig, m_repoRoot, *m_editorContext); m_editorContext->SetExitRequestHandler([this]() { if (m_windowManager == nullptr) { return; } if (App::EditorWindow* primaryWindow = m_windowManager->FindPrimaryWindow(); primaryWindow != nullptr && primaryWindow->GetHwnd() != nullptr) { PostMessageW(primaryWindow->GetHwnd(), WM_CLOSE, 0, 0); } }); m_editorContext->SetReadyStatus(); App::EditorWindowManager::CreateParams createParams = {}; createParams.windowId = "main"; createParams.title = kWindowTitle; createParams.showCommand = nCmdShow; createParams.primary = true; createParams.autoCaptureOnStartup = true; if (m_windowManager->CreateEditorWindow( m_editorContext->BuildWorkspaceController(), createParams) == nullptr) { AppendUIEditorRuntimeTrace("app", "primary window creation failed"); return false; } AppendUIEditorRuntimeTrace("app", "initialize end"); return true; } void Application::Shutdown() { AppendUIEditorRuntimeTrace("app", "shutdown begin"); if (m_windowManager != nullptr) { m_windowManager->Shutdown(); m_windowManager.reset(); } if (m_editorContext != nullptr) { m_editorContext->SetSystemInteractionHost(nullptr); } m_systemInteractionHost.reset(); ::XCEngine::Resources::ResourceManager::Get().Shutdown(); if (m_windowClassAtom != 0 && m_hInstance != nullptr) { UnregisterClassW(kWindowClassName, m_hInstance); m_windowClassAtom = 0; } AppendUIEditorRuntimeTrace("app", "shutdown end"); ShutdownUIEditorRuntimeTrace(); } std::filesystem::path Application::ResolveRepoRootPath() { std::string root = XCUIEDITOR_REPO_ROOT; if (root.size() >= 2u && root.front() == '"' && root.back() == '"') { root = root.substr(1u, root.size() - 2u); } return std::filesystem::path(root).lexically_normal(); } LONG WINAPI Application::HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo) { if (exceptionInfo != nullptr && exceptionInfo->ExceptionRecord != nullptr) { AppendUIEditorCrashTrace( exceptionInfo->ExceptionRecord->ExceptionCode, exceptionInfo->ExceptionRecord->ExceptionAddress); } else { AppendUIEditorCrashTrace(0u, nullptr); } return EXCEPTION_EXECUTE_HANDLER; } } // namespace XCEngine::UI::Editor namespace XCEngine::UI::Editor { int Application::Run(HINSTANCE hInstance, int nCmdShow) { if (!Initialize(hInstance, nCmdShow)) { Shutdown(); return 1; } constexpr int kMaxMessagesPerTick = 64; MSG message = {}; while (true) { int processedMessageCount = 0; while (processedMessageCount < kMaxMessagesPerTick && PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) { if (message.message == WM_QUIT) { Shutdown(); return static_cast(message.wParam); } TranslateMessage(&message); DispatchMessageW(&message); ++processedMessageCount; } if (m_windowManager != nullptr) { ::XCEngine::Resources::ResourceManager::Get().UpdateAsyncLoads(); m_windowManager->DestroyClosedWindows(); if (!m_windowManager->HasWindows()) { break; } m_windowManager->RenderAllWindows(); } else { break; } } Shutdown(); return 0; } } // namespace XCEngine::UI::Editor namespace XCEngine::UI::Editor { namespace { void TryEnableNonClientDpiScaling(HWND hwnd) { if (hwnd == nullptr) { return; } const HMODULE user32 = GetModuleHandleW(L"user32.dll"); if (user32 == nullptr) { return; } using EnableNonClientDpiScalingFn = BOOL(WINAPI*)(HWND); const auto enableNonClientDpiScaling = reinterpret_cast( GetProcAddress(user32, "EnableNonClientDpiScaling")); if (enableNonClientDpiScaling != nullptr) { enableNonClientDpiScaling(hwnd); } } Application* GetApplicationFromWindowUserData(HWND hwnd) { return reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); } } // namespace bool Application::RegisterWindowClass() { WNDCLASSEXW windowClass = {}; windowClass.cbSize = sizeof(windowClass); windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; windowClass.lpfnWndProc = &Application::WndProc; windowClass.cbClsExtra = 0; windowClass.cbWndExtra = 0; windowClass.hInstance = m_hInstance; windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); windowClass.hbrBackground = nullptr; windowClass.lpszMenuName = nullptr; windowClass.hIcon = static_cast( LoadImageW( m_hInstance, MAKEINTRESOURCEW(IDI_APP_ICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE)); windowClass.hIconSm = static_cast( LoadImageW( m_hInstance, MAKEINTRESOURCEW(IDI_APP_ICON_SMALL), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR)); windowClass.lpszClassName = kWindowClassName; m_windowClassAtom = RegisterClassExW(&windowClass); if (m_windowClassAtom == 0) { AppendUIEditorRuntimeTrace("app", "window class registration failed"); return false; } return true; } LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message == WM_NCCREATE) { TryEnableNonClientDpiScaling(hwnd); const auto* createStruct = reinterpret_cast(lParam); Application* application = createStruct != nullptr ? reinterpret_cast(createStruct->lpCreateParams) : nullptr; SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(application)); if (application != nullptr && application->m_windowManager != nullptr) { application->m_windowManager->HandlePendingNativeWindowCreated(hwnd); } return TRUE; } Application* application = GetApplicationFromWindowUserData(hwnd); LRESULT dispatcherResult = 0; if (application != nullptr && application->m_windowManager != nullptr && application->m_windowManager->TryDispatchWindowMessage( hwnd, message, wParam, lParam, dispatcherResult)) { return dispatcherResult; } return DefWindowProcW(hwnd, message, wParam, lParam); } } // namespace XCEngine::UI::Editor