382 lines
12 KiB
C++
382 lines
12 KiB
C++
#include "Platform/Win32/Windowing/EditorWindowHostRuntime.h"
|
|
|
|
#include "Bootstrap/EditorResources.h"
|
|
#include "Platform/Win32/Chrome/EditorWindowChromeController.h"
|
|
#include "Platform/Win32/Windowing/EditorFloatingWindowPlacement.h"
|
|
#include "Platform/Win32/Windowing/EditorWindow.h"
|
|
#include "Windowing/Host/EditorWindowHostCoordinator.h"
|
|
#include "Windowing/Runtime/EditorWindowRuntimeController.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->GetWindowId()
|
|
<< "{hwnd=" << DescribeHwnd(window->GetHwnd())
|
|
<< ",primary=" << (window->IsPrimary() ? '1' : '0')
|
|
<< ",state=" << GetEditorWindowLifecycleStateName(window->GetLifecycleState())
|
|
<< '}';
|
|
}
|
|
stream << ']';
|
|
return stream.str();
|
|
}
|
|
|
|
} // 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;
|
|
|
|
EditorWindow* EditorWindowHostRuntime::CreateHostWindow(
|
|
std::unique_ptr<EditorWindowRuntimeController> runtimeController,
|
|
const EditorWindowCreateParams& params) {
|
|
if (runtimeController == nullptr) {
|
|
LogRuntimeTrace("window", "window creation failed: runtime controller is null");
|
|
return nullptr;
|
|
}
|
|
|
|
const EditorWindowContentCapabilities capabilities =
|
|
runtimeController->GetCapabilities();
|
|
if (params.category == EditorWindowCategory::Workspace && !capabilities.workspace) {
|
|
LogRuntimeTrace("window", "workspace window creation rejected: content is not workspace");
|
|
return nullptr;
|
|
}
|
|
if (params.category == EditorWindowCategory::Utility && !capabilities.utilityPanel) {
|
|
LogRuntimeTrace("window", "utility window creation rejected: content is not utility");
|
|
return nullptr;
|
|
}
|
|
|
|
auto windowPtr = std::make_unique<EditorWindow>(
|
|
params.windowId,
|
|
params.title.empty() ? std::wstring(L"XCEngine Editor") : params.title,
|
|
params.category,
|
|
params.chromePolicy,
|
|
params.primary,
|
|
std::move(runtimeController));
|
|
EditorWindow* const rawWindow = windowPtr.get();
|
|
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 = params.nativeStylePolicy.useHostWindowStyle
|
|
? m_hostConfig.windowStyle
|
|
: static_cast<DWORD>(params.nativeStylePolicy.windowStyle);
|
|
const DWORD extendedWindowStyle =
|
|
params.nativeStylePolicy.extendedWindowStyle != 0u
|
|
? static_cast<DWORD>(params.nativeStylePolicy.extendedWindowStyle)
|
|
: WS_EX_APPWINDOW;
|
|
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().c_str(),
|
|
windowStyle,
|
|
initialX,
|
|
initialY,
|
|
params.initialWidth,
|
|
params.initialHeight,
|
|
nullptr,
|
|
nullptr,
|
|
m_hostConfig.hInstance,
|
|
m_hostConfig.windowUserData);
|
|
m_pendingCreateWindow = nullptr;
|
|
if (hwnd == nullptr) {
|
|
eraseRawWindow();
|
|
return nullptr;
|
|
}
|
|
|
|
if (!rawWindow->HasHwnd()) {
|
|
rawWindow->AttachHwnd(hwnd);
|
|
}
|
|
|
|
auto failWindowInitialization = [&](std::string_view message) {
|
|
LogRuntimeTrace("window", std::string(message));
|
|
if (m_hostCoordinator != nullptr) {
|
|
m_hostCoordinator->AbortUnregisteredWindow(*rawWindow);
|
|
} else {
|
|
eraseRawWindow();
|
|
}
|
|
return static_cast<EditorWindow*>(nullptr);
|
|
};
|
|
|
|
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(
|
|
*rawWindow,
|
|
EditorHostWindowRuntimeInitializationParams{
|
|
.repoRoot = m_repoRoot,
|
|
.captureRoot = m_captureRoot,
|
|
.autoCaptureOnStartup = params.autoCaptureOnStartup,
|
|
})) {
|
|
return failWindowInitialization("managed window initialization failed");
|
|
}
|
|
|
|
ShowWindow(hwnd, params.showCommand);
|
|
UpdateWindow(hwnd);
|
|
return rawWindow;
|
|
}
|
|
|
|
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.get());
|
|
}
|
|
}
|
|
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.get());
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
return FindWindow(GetAncestor(hitWindow, GA_ROOT));
|
|
}
|
|
|
|
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->IsDestroyed()) {
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
if (m_pendingCreateWindow == window) {
|
|
m_pendingCreateWindow = nullptr;
|
|
}
|
|
|
|
LogRuntimeTrace(
|
|
"window-close",
|
|
"ReapDestroyedWindows erase windowId='" + std::string(window->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->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) {
|
|
return FindWindowByIdImpl(windowId);
|
|
}
|
|
|
|
const EditorHostWindow* EditorWindowHostRuntime::FindWindowById(std::string_view windowId) const {
|
|
return FindWindowByIdImpl(windowId);
|
|
}
|
|
|
|
EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() {
|
|
for (const std::unique_ptr<EditorWindow>& window : m_windows) {
|
|
if (window != nullptr && window->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
|