2026-04-15 08:24:06 +08:00
|
|
|
#include "Platform/Win32/EditorWindow.h"
|
2026-04-17 22:32:58 +08:00
|
|
|
|
|
|
|
|
#include "Bootstrap/EditorResources.h"
|
|
|
|
|
#include "Platform/Win32/EditorWindowConstants.h"
|
2026-04-19 02:48:41 +08:00
|
|
|
#include "Platform/Win32/EditorWindowInternalState.h"
|
2026-04-17 22:32:58 +08:00
|
|
|
#include "Platform/Win32/EditorWindowPlatformInternal.h"
|
2026-04-15 22:47:42 +08:00
|
|
|
#include "Platform/Win32/EditorWindowRuntimeInternal.h"
|
2026-04-17 22:32:58 +08:00
|
|
|
#include "State/EditorContext.h"
|
|
|
|
|
|
|
|
|
|
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
|
|
|
|
#include <XCEngine/UI/Types.h>
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
|
|
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<UINT>(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<GetDpiForWindowFn>(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<DWORD>(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
|
2026-04-15 08:24:06 +08:00
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
|
|
2026-04-15 22:47:42 +08:00
|
|
|
using namespace EditorWindowInternal;
|
2026-04-17 22:32:58 +08:00
|
|
|
using ::XCEngine::UI::UIPoint;
|
2026-04-15 08:24:06 +08:00
|
|
|
|
|
|
|
|
EditorWindow::EditorWindow(
|
|
|
|
|
std::string windowId,
|
|
|
|
|
std::wstring title,
|
|
|
|
|
bool primary,
|
|
|
|
|
UIEditorWorkspaceController workspaceController)
|
2026-04-19 02:48:41 +08:00
|
|
|
: m_state(std::make_unique<EditorWindowState>()) {
|
|
|
|
|
m_state->window.windowId = std::move(windowId);
|
|
|
|
|
m_state->window.title = std::move(title);
|
|
|
|
|
m_state->window.primary = primary;
|
|
|
|
|
m_state->composition.workspaceController = std::move(workspaceController);
|
2026-04-15 08:24:06 +08:00
|
|
|
UpdateCachedTitleText();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
EditorWindow::~EditorWindow() = default;
|
|
|
|
|
|
2026-04-15 08:24:06 +08:00
|
|
|
std::string_view EditorWindow::GetWindowId() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->window.windowId;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HWND EditorWindow::GetHwnd() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->window.hwnd;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::HasHwnd() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->window.hwnd != nullptr;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::IsPrimary() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->window.primary;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 22:32:58 +08:00
|
|
|
bool EditorWindow::IsClosing() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->window.closing;
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-15 08:24:06 +08:00
|
|
|
bool EditorWindow::IsRenderReady() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->render.ready;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::IsTrackingMouseLeave() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->input.trackingMouseLeave;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::HasHoveredBorderlessResizeEdge() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() !=
|
2026-04-15 08:24:06 +08:00
|
|
|
Host::BorderlessWindowResizeEdge::None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::wstring& EditorWindow::GetTitle() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->window.title;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->composition.workspaceController;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-19 04:36:52 +08:00
|
|
|
UIEditorWorkspaceController& EditorWindow::GetMutableWorkspaceController() {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->composition.workspaceController;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const EditorShellRuntime& EditorWindow::GetShellRuntime() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->composition.shellRuntime;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorShellRuntime& EditorWindow::GetShellRuntime() {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->composition.shellRuntime;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIEditorShellInteractionFrame& EditorWindow::GetShellFrame() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->composition.shellRuntime.GetShellFrame();
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIEditorShellInteractionState& EditorWindow::GetShellInteractionState() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->composition.shellRuntime.GetShellInteractionState();
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-15 13:22:58 +08:00
|
|
|
void EditorWindow::SetExternalDockHostDropPreview(
|
|
|
|
|
const Widgets::UIEditorDockHostDropPreviewState& preview) {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->composition.shellRuntime.SetExternalDockHostDropPreview(preview);
|
2026-04-15 13:22:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::ClearExternalDockHostDropPreview() {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->composition.shellRuntime.ClearExternalDockHostDropPreview();
|
2026-04-15 13:22:58 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-15 08:24:06 +08:00
|
|
|
void EditorWindow::AttachHwnd(HWND hwnd) {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->window.hwnd = hwnd;
|
|
|
|
|
m_state->window.closing = false;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::MarkDestroyed() {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->window.hwnd = nullptr;
|
|
|
|
|
m_state->window.closing = false;
|
|
|
|
|
m_state->input.trackingMouseLeave = false;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 22:32:58 +08:00
|
|
|
void EditorWindow::MarkClosing() {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->window.closing = true;
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::ClearClosing() {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->window.closing = false;
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-15 08:24:06 +08:00
|
|
|
void EditorWindow::SetTrackingMouseLeave(bool trackingMouseLeave) {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->input.trackingMouseLeave = trackingMouseLeave;
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::SetTitle(std::wstring title) {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->window.title = std::move(title);
|
2026-04-15 08:24:06 +08:00
|
|
|
UpdateCachedTitleText();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->composition.workspaceController = std::move(workspaceController);
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::InvalidateHostWindow() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
if (m_state->window.hwnd != nullptr && IsWindow(m_state->window.hwnd)) {
|
|
|
|
|
InvalidateRect(m_state->window.hwnd, nullptr, FALSE);
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 22:32:58 +08:00
|
|
|
bool EditorWindow::Initialize(
|
|
|
|
|
const std::filesystem::path& repoRoot,
|
|
|
|
|
EditorContext& editorContext,
|
|
|
|
|
const std::filesystem::path& captureRoot,
|
|
|
|
|
bool autoCaptureOnStartup) {
|
2026-04-19 02:48:41 +08:00
|
|
|
if (m_state->window.hwnd == nullptr) {
|
2026-04-17 22:32:58 +08:00
|
|
|
LogRuntimeTrace("app", "window initialize skipped: hwnd is null");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd);
|
|
|
|
|
m_state->chrome.runtime.Reset();
|
|
|
|
|
m_state->chrome.runtime.SetWindowDpi(QueryWindowDpi(m_state->window.hwnd));
|
|
|
|
|
m_state->render.renderer.SetDpiScale(GetDpiScale());
|
2026-04-17 22:32:58 +08:00
|
|
|
|
|
|
|
|
std::ostringstream dpiTrace = {};
|
2026-04-19 02:48:41 +08:00
|
|
|
dpiTrace << "initial dpi=" << m_state->chrome.runtime.GetWindowDpi()
|
2026-04-17 22:32:58 +08:00
|
|
|
<< " scale=" << GetDpiScale();
|
|
|
|
|
LogRuntimeTrace("window", dpiTrace.str());
|
|
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
if (!m_state->render.renderer.Initialize(m_state->window.hwnd)) {
|
2026-04-17 22:32:58 +08:00
|
|
|
LogRuntimeTrace("app", "renderer initialization failed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RECT clientRect = {};
|
2026-04-19 02:48:41 +08:00
|
|
|
GetClientRect(m_state->window.hwnd, &clientRect);
|
2026-04-17 22:32:58 +08:00
|
|
|
const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L);
|
|
|
|
|
const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L);
|
2026-04-19 02:48:41 +08:00
|
|
|
if (!m_state->render.windowRenderer.Initialize(
|
|
|
|
|
m_state->window.hwnd,
|
|
|
|
|
clientWidth,
|
|
|
|
|
clientHeight)) {
|
2026-04-17 22:32:58 +08:00
|
|
|
LogRuntimeTrace("app", "d3d12 window renderer initialization failed");
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->render.renderer.Shutdown();
|
2026-04-17 22:32:58 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Host::D3D12WindowRenderLoopAttachResult attachResult =
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->render.windowRenderLoop.Attach(
|
|
|
|
|
m_state->render.renderer,
|
|
|
|
|
m_state->render.windowRenderer);
|
2026-04-17 22:32:58 +08:00
|
|
|
if (!attachResult.interopWarning.empty()) {
|
|
|
|
|
LogRuntimeTrace("app", attachResult.interopWarning);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
editorContext.AttachTextMeasurer(m_state->render.renderer);
|
|
|
|
|
m_state->composition.shellRuntime.Initialize(
|
|
|
|
|
repoRoot,
|
|
|
|
|
m_state->render.renderer,
|
|
|
|
|
m_state->render.renderer);
|
|
|
|
|
m_state->composition.shellRuntime.AttachViewportWindowRenderer(
|
|
|
|
|
m_state->render.windowRenderer);
|
|
|
|
|
m_state->composition.shellRuntime.SetViewportSurfacePresentationEnabled(
|
2026-04-17 22:32:58 +08:00
|
|
|
attachResult.hasViewportSurfacePresentation);
|
|
|
|
|
|
|
|
|
|
std::string titleBarLogoError = {};
|
|
|
|
|
if (!LoadEmbeddedPngTexture(
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->render.renderer,
|
2026-04-17 22:32:58 +08:00
|
|
|
IDR_PNG_LOGO_ICON,
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->render.titleBarLogoIcon,
|
2026-04-17 22:32:58 +08:00
|
|
|
titleBarLogoError)) {
|
|
|
|
|
LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError);
|
|
|
|
|
}
|
2026-04-19 02:48:41 +08:00
|
|
|
if (!m_state->composition.shellRuntime.GetBuiltInIconError().empty()) {
|
|
|
|
|
LogRuntimeTrace("icons", m_state->composition.shellRuntime.GetBuiltInIconError());
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LogRuntimeTrace(
|
|
|
|
|
"app",
|
|
|
|
|
"shell runtime initialized: " +
|
|
|
|
|
editorContext.DescribeWorkspaceState(
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->composition.workspaceController,
|
|
|
|
|
m_state->composition.shellRuntime.GetShellInteractionState()));
|
|
|
|
|
m_state->render.ready = true;
|
2026-04-17 22:32:58 +08:00
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->render.autoScreenshot.Initialize(captureRoot);
|
2026-04-17 22:32:58 +08:00
|
|
|
if (autoCaptureOnStartup && IsAutoCaptureOnStartupEnabled()) {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->render.autoScreenshot.RequestCapture("startup");
|
2026-04-17 22:32:58 +08:00
|
|
|
editorContext.SetStatus("Capture", "Startup capture requested.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::Shutdown() {
|
2026-04-19 00:03:25 +08:00
|
|
|
ForceReleasePointerCapture();
|
2026-04-17 22:32:58 +08:00
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->render.ready = false;
|
|
|
|
|
m_state->render.autoScreenshot.Shutdown();
|
|
|
|
|
m_state->composition.shellRuntime.Shutdown();
|
|
|
|
|
m_state->render.renderer.ReleaseTexture(m_state->render.titleBarLogoIcon);
|
|
|
|
|
m_state->render.windowRenderLoop.Detach();
|
|
|
|
|
m_state->render.windowRenderer.Shutdown();
|
|
|
|
|
m_state->render.renderer.Shutdown();
|
|
|
|
|
m_state->input.pendingEvents.clear();
|
|
|
|
|
m_state->chrome.chromeState = {};
|
|
|
|
|
m_state->chrome.runtime.Reset();
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::ResetInteractionState() {
|
2026-04-19 00:03:25 +08:00
|
|
|
ForceReleasePointerCapture();
|
2026-04-17 22:32:58 +08:00
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->input.pendingEvents.clear();
|
|
|
|
|
m_state->input.trackingMouseLeave = false;
|
|
|
|
|
m_state->input.modifierTracker.Reset();
|
|
|
|
|
m_state->composition.shellRuntime.ResetInteractionState();
|
|
|
|
|
m_state->chrome.chromeState = {};
|
|
|
|
|
m_state->chrome.runtime.EndBorderlessResize();
|
|
|
|
|
m_state->chrome.runtime.EndBorderlessWindowDragRestore();
|
|
|
|
|
m_state->chrome.runtime.EndInteractiveResize();
|
|
|
|
|
m_state->chrome.runtime.SetHoveredBorderlessResizeEdge(
|
|
|
|
|
Host::BorderlessWindowResizeEdge::None);
|
|
|
|
|
m_state->chrome.runtime.ClearPredictedClientPixelSize();
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::ApplyWindowResize(UINT width, UINT height) {
|
2026-04-19 02:48:41 +08:00
|
|
|
if (!m_state->render.ready || width == 0u || height == 0u) {
|
2026-04-17 22:32:58 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Host::D3D12WindowRenderLoopResizeResult resizeResult =
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->render.windowRenderLoop.ApplyResize(width, height);
|
|
|
|
|
m_state->composition.shellRuntime.SetViewportSurfacePresentationEnabled(
|
2026-04-17 22:32:58 +08:00
|
|
|
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;
|
2026-04-19 02:48:41 +08:00
|
|
|
if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) {
|
2026-04-17 22:32:58 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RECT clientRect = {};
|
2026-04-19 02:48:41 +08:00
|
|
|
if (!GetClientRect(m_state->window.hwnd, &clientRect)) {
|
2026-04-17 22:32:58 +08:00
|
|
|
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<UINT>(width);
|
|
|
|
|
outHeight = static_cast<UINT>(height);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const {
|
2026-04-19 02:48:41 +08:00
|
|
|
if (m_state->chrome.runtime.TryGetPredictedClientPixelSize(outWidth, outHeight)) {
|
2026-04-17 22:32:58 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return QueryCurrentClientPixelSize(outWidth, outHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float EditorWindow::GetDpiScale() const {
|
2026-04-19 02:48:41 +08:00
|
|
|
return m_state->chrome.runtime.GetDpiScale(kBaseDpiScale);
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<float>(x)),
|
|
|
|
|
PixelsToDips(static_cast<float>(y)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIPoint EditorWindow::ConvertScreenPixelsToClientDips(const POINT& screenPoint) const {
|
|
|
|
|
POINT clientPoint = screenPoint;
|
2026-04-19 02:48:41 +08:00
|
|
|
if (m_state->window.hwnd != nullptr) {
|
|
|
|
|
ScreenToClient(m_state->window.hwnd, &clientPoint);
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
const float dpiScale = m_state->chrome.runtime.GetDpiScale(kBaseDpiScale);
|
2026-04-17 22:32:58 +08:00
|
|
|
return UIPoint(
|
|
|
|
|
dpiScale > 0.0f
|
|
|
|
|
? static_cast<float>(clientPoint.x) / dpiScale
|
|
|
|
|
: static_cast<float>(clientPoint.x),
|
|
|
|
|
dpiScale > 0.0f
|
|
|
|
|
? static_cast<float>(clientPoint.y) / dpiScale
|
|
|
|
|
: static_cast<float>(clientPoint.y));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::OnResize(UINT width, UINT height) {
|
|
|
|
|
bool matchesPredictedClientSize = false;
|
|
|
|
|
UINT predictedWidth = 0u;
|
|
|
|
|
UINT predictedHeight = 0u;
|
2026-04-19 02:48:41 +08:00
|
|
|
if (m_state->chrome.runtime.TryGetPredictedClientPixelSize(
|
|
|
|
|
predictedWidth,
|
|
|
|
|
predictedHeight)) {
|
2026-04-17 22:32:58 +08:00
|
|
|
matchesPredictedClientSize =
|
|
|
|
|
predictedWidth == width &&
|
|
|
|
|
predictedHeight == height;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->chrome.runtime.ClearPredictedClientPixelSize();
|
|
|
|
|
if (IsBorderlessWindowEnabled() && m_state->window.hwnd != nullptr) {
|
|
|
|
|
Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd);
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!matchesPredictedClientSize) {
|
|
|
|
|
ApplyWindowResize(width, height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::OnEnterSizeMove() {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->chrome.runtime.BeginInteractiveResize();
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::OnExitSizeMove() {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->chrome.runtime.EndInteractiveResize();
|
|
|
|
|
m_state->chrome.runtime.ClearPredictedClientPixelSize();
|
2026-04-17 22:32:58 +08:00
|
|
|
UINT width = 0u;
|
|
|
|
|
UINT height = 0u;
|
|
|
|
|
if (QueryCurrentClientPixelSize(width, height)) {
|
|
|
|
|
ApplyWindowResize(width, height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->chrome.runtime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi);
|
|
|
|
|
m_state->render.renderer.SetDpiScale(GetDpiScale());
|
|
|
|
|
if (m_state->window.hwnd != nullptr) {
|
2026-04-17 22:32:58 +08:00
|
|
|
const LONG windowWidth = suggestedRect.right - suggestedRect.left;
|
|
|
|
|
const LONG windowHeight = suggestedRect.bottom - suggestedRect.top;
|
|
|
|
|
SetWindowPos(
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->window.hwnd,
|
2026-04-17 22:32:58 +08:00
|
|
|
nullptr,
|
|
|
|
|
suggestedRect.left,
|
|
|
|
|
suggestedRect.top,
|
|
|
|
|
windowWidth,
|
|
|
|
|
windowHeight,
|
|
|
|
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
|
|
|
UINT clientWidth = 0u;
|
|
|
|
|
UINT clientHeight = 0u;
|
|
|
|
|
if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) {
|
|
|
|
|
ApplyWindowResize(clientWidth, clientHeight);
|
|
|
|
|
}
|
2026-04-19 02:48:41 +08:00
|
|
|
Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd);
|
2026-04-17 22:32:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::ostringstream trace = {};
|
2026-04-19 02:48:41 +08:00
|
|
|
trace << "dpi changed to " << m_state->chrome.runtime.GetWindowDpi()
|
2026-04-17 22:32:58 +08:00
|
|
|
<< " scale=" << GetDpiScale();
|
|
|
|
|
LogRuntimeTrace("window", trace.str());
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 08:24:06 +08:00
|
|
|
bool EditorWindow::IsVerboseRuntimeTraceEnabled() {
|
|
|
|
|
static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled();
|
|
|
|
|
return s_enabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::UpdateCachedTitleText() {
|
2026-04-19 02:48:41 +08:00
|
|
|
m_state->window.titleText = WideToUtf8(m_state->window.title);
|
2026-04-15 08:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|