Files
XCEngine/editor/app/Host/Win32/Windowing/EditorWindowHostRuntime.cpp
2026-04-27 22:21:40 +08:00

383 lines
12 KiB
C++

#include "Windowing/EditorWindowHostRuntime.h"
#include "EditorResources.h"
#include "Chrome/EditorWindowChromeController.h"
#include "Windowing/EditorFloatingWindowPlacement.h"
#include "Windowing/EditorWindow.h"
#include "EditorWindowHostCoordinator.h"
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <algorithm>
#include <cstdint>
#include <sstream>
namespace XCEngine::UI::Editor::App {
namespace {
std::string DescribeHwnd(HWND hwnd) {
std::ostringstream stream = {};
stream << "0x" << std::hex << std::uppercase
<< reinterpret_cast<std::uintptr_t>(hwnd);
return stream.str();
}
std::string DescribeHostWindows(
const std::vector<std::unique_ptr<EditorWindow>>& windows) {
std::ostringstream stream = {};
stream << "count=" << windows.size() << " [";
bool first = true;
for (const std::unique_ptr<EditorWindow>& window : windows) {
if (!first) {
stream << ", ";
}
first = false;
if (window == nullptr) {
stream << "<null>";
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<EditorWindow>(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<EditorWindow>& 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<HICON>(
LoadImageW(
m_hostConfig.hInstance,
MAKEINTRESOURCEW(IDI_APP_ICON),
IMAGE_ICON,
0,
0,
LR_DEFAULTSIZE));
const HICON smallIcon = static_cast<HICON>(
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<LPARAM>(bigIcon));
}
if (smallIcon != nullptr) {
SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(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<EditorHostWindow*> EditorWindowHostRuntime::GetWindows() {
std::vector<EditorHostWindow*> windows = {};
windows.reserve(m_windows.size());
for (const std::unique_ptr<EditorWindow>& window : m_windows) {
if (window != nullptr) {
windows.push_back(&window->GetOwner());
}
}
return windows;
}
std::vector<const EditorHostWindow*> EditorWindowHostRuntime::GetWindows() const {
std::vector<const EditorHostWindow*> windows = {};
windows.reserve(m_windows.size());
for (const std::unique_ptr<EditorWindow>& 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<EditorWindowHostRuntime*>(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<EditorWindow>& window : m_windows) {
if (window != nullptr && window->GetHwnd() == hwnd) {
return window.get();
}
}
return nullptr;
}
const EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) const {
return const_cast<EditorWindowHostRuntime*>(this)->FindWindow(hwnd);
}
EditorWindow* EditorWindowHostRuntime::FindWindowByIdImpl(std::string_view windowId) {
if (windowId.empty()) {
return nullptr;
}
for (const std::unique_ptr<EditorWindow>& 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<EditorWindowHostRuntime*>(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<EditorWindow>& window : m_windows) {
if (window != nullptr && window->GetOwner().IsPrimary()) {
return window.get();
}
}
return nullptr;
}
const EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() const {
return const_cast<EditorWindowHostRuntime*>(this)->FindPrimaryWindow();
}
void EditorWindowHostRuntime::LogRuntimeTrace(
std::string_view channel,
std::string_view message) const {
AppendUIEditorRuntimeTrace(channel, message);
}
} // namespace XCEngine::UI::Editor::App