2026-04-25 16:46:01 +08:00
|
|
|
#include "Platform/Win32/Windowing/EditorWindow.h"
|
|
|
|
|
#include "Bootstrap/EditorResources.h"
|
|
|
|
|
#include "Platform/Win32/Chrome/EditorWindowChromeController.h"
|
2026-04-25 18:20:17 +08:00
|
|
|
#include "Windowing/Content/EditorWindowContentController.h"
|
2026-04-25 16:46:01 +08:00
|
|
|
#include "Platform/Win32/Runtime/EditorWindowFrameDriver.h"
|
|
|
|
|
#include "Platform/Win32/Windowing/EditorWindowSession.h"
|
|
|
|
|
#include "Platform/Win32/Windowing/EditorWindowSupport.h"
|
2026-04-25 18:20:17 +08:00
|
|
|
#include "Windowing/Frame/EditorWindowFrameOrchestrator.h"
|
2026-04-25 16:46:01 +08:00
|
|
|
#include "Platform/Win32/Runtime/EditorWindowInputController.h"
|
|
|
|
|
#include "Platform/Win32/Runtime/EditorWindowRuntimeController.h"
|
|
|
|
|
#include "Composition/EditorContext.h"
|
|
|
|
|
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
|
|
|
|
|
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
|
|
|
|
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
|
|
|
|
#include <XCEngine/UI/Types.h>
|
|
|
|
|
#include <cassert>
|
|
|
|
|
#include <algorithm>
|
2026-04-25 19:25:49 +08:00
|
|
|
#include <optional>
|
2026-04-25 16:46:01 +08:00
|
|
|
#include <sstream>
|
|
|
|
|
#include <windowsx.h>
|
|
|
|
|
#include <XCEngine/UI/DrawData.h>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <utility>
|
|
|
|
|
#include <XCEditor/Shell/UIEditorShellCapturePolicy.h>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::App::EditorWindowSupport {
|
|
|
|
|
|
|
|
|
|
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() {
|
2026-04-25 19:25:49 +08:00
|
|
|
return IsEditorWindowVerboseRuntimeTraceEnabled();
|
2026-04-25 16:46:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LogRuntimeTrace(std::string_view channel, std::string_view message) {
|
|
|
|
|
AppendUIEditorRuntimeTrace(channel, message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::App::EditorWindowSupport
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
|
|
|
|
|
|
using namespace EditorWindowSupport;
|
|
|
|
|
using ::XCEngine::UI::UIPoint;
|
|
|
|
|
|
|
|
|
|
EditorWindow::EditorWindow(
|
|
|
|
|
std::string windowId,
|
|
|
|
|
std::wstring title,
|
2026-04-25 17:51:37 +08:00
|
|
|
EditorWindowCategory category,
|
|
|
|
|
EditorWindowChromePolicy chromePolicy,
|
2026-04-25 16:46:01 +08:00
|
|
|
bool primary,
|
|
|
|
|
std::unique_ptr<EditorWindowContentController> contentController)
|
|
|
|
|
: m_session(std::make_unique<EditorWindowSession>(
|
|
|
|
|
std::move(windowId),
|
|
|
|
|
std::move(title),
|
2026-04-25 17:51:37 +08:00
|
|
|
category,
|
|
|
|
|
chromePolicy,
|
2026-04-25 16:46:01 +08:00
|
|
|
primary))
|
|
|
|
|
, m_chromeController(std::make_unique<EditorWindowChromeController>())
|
|
|
|
|
, m_frameOrchestrator(std::make_unique<EditorWindowFrameOrchestrator>())
|
|
|
|
|
, m_inputController(std::make_unique<EditorWindowInputController>())
|
|
|
|
|
, m_runtime(std::make_unique<EditorWindowRuntimeController>(
|
|
|
|
|
std::move(contentController))) {}
|
|
|
|
|
|
|
|
|
|
EditorWindow::~EditorWindow() = default;
|
|
|
|
|
|
|
|
|
|
std::string_view EditorWindow::GetWindowId() const {
|
|
|
|
|
return m_session->GetWindowId();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HWND EditorWindow::GetHwnd() const {
|
|
|
|
|
return m_session->GetHwnd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::HasHwnd() const {
|
|
|
|
|
return m_session->HasHwnd();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 17:51:37 +08:00
|
|
|
EditorWindowCategory EditorWindow::GetCategory() const {
|
|
|
|
|
return m_session->GetCategory();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const EditorWindowChromePolicy& EditorWindow::GetChromePolicy() const {
|
|
|
|
|
return m_session->GetChromePolicy();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 16:46:01 +08:00
|
|
|
EditorWindowLifecycleState EditorWindow::GetLifecycleState() const {
|
|
|
|
|
return m_session->GetLifecycleState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::IsPrimary() const {
|
|
|
|
|
return m_session->IsPrimary();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 17:51:37 +08:00
|
|
|
bool EditorWindow::IsWorkspaceWindow() const {
|
|
|
|
|
return m_session->IsWorkspaceWindow();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::IsUtilityWindow() const {
|
|
|
|
|
return m_session->IsUtilityWindow();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 16:46:01 +08:00
|
|
|
bool EditorWindow::IsClosing() const {
|
|
|
|
|
return m_session->IsClosing();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::IsDestroyed() const {
|
|
|
|
|
return m_session->IsDestroyed();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::IsRenderReady() const {
|
|
|
|
|
return m_runtime->IsReady();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::wstring& EditorWindow::GetTitle() const {
|
|
|
|
|
return m_session->GetTitle();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string_view EditorWindow::GetCachedTitleText() const {
|
|
|
|
|
return m_session->GetCachedTitleText();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIEditorWorkspaceController* EditorWindow::TryGetWorkspaceController() const {
|
|
|
|
|
const EditorWindowWorkspaceBinding* workspaceBinding = m_runtime->TryGetWorkspaceBinding();
|
|
|
|
|
return workspaceBinding != nullptr
|
|
|
|
|
? workspaceBinding->TryGetWorkspaceController()
|
|
|
|
|
: nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const {
|
|
|
|
|
const UIEditorWorkspaceController* workspaceController = TryGetWorkspaceController();
|
|
|
|
|
assert(workspaceController != nullptr);
|
|
|
|
|
return *workspaceController;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 19:25:49 +08:00
|
|
|
EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() {
|
|
|
|
|
return m_runtime->TryGetDockHostBinding();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() const {
|
|
|
|
|
return m_runtime->TryGetDockHostBinding();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 16:46:01 +08:00
|
|
|
void EditorWindow::AttachHwnd(HWND hwnd) {
|
|
|
|
|
m_session->AttachHwnd(hwnd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::MarkInitializing() {
|
|
|
|
|
m_session->MarkInitializing();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::MarkRunning() {
|
|
|
|
|
m_session->MarkRunning();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::MarkDestroyed() {
|
|
|
|
|
m_session->MarkDestroyed();
|
|
|
|
|
m_inputController->ResetWindowState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::MarkClosing() {
|
|
|
|
|
m_session->MarkClosing();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::SetPrimary(bool primary) {
|
|
|
|
|
m_session->SetPrimary(primary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::SetTitle(std::wstring title) {
|
|
|
|
|
m_session->SetTitle(std::move(title));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) {
|
|
|
|
|
EditorWindowWorkspaceBinding* workspaceBinding = m_runtime->TryGetWorkspaceBinding();
|
|
|
|
|
assert(workspaceBinding != nullptr);
|
|
|
|
|
workspaceBinding->ReplaceWorkspaceController(std::move(workspaceController));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::InvalidateHostWindow() const {
|
|
|
|
|
if (const HWND hwnd = m_session->GetHwnd();
|
|
|
|
|
hwnd != nullptr && IsWindow(hwnd)) {
|
|
|
|
|
InvalidateRect(hwnd, nullptr, FALSE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::Initialize(
|
|
|
|
|
const std::filesystem::path& repoRoot,
|
|
|
|
|
EditorContext& editorContext,
|
|
|
|
|
const std::filesystem::path& captureRoot,
|
|
|
|
|
bool autoCaptureOnStartup) {
|
|
|
|
|
if (m_session->GetHwnd() == nullptr) {
|
|
|
|
|
LogRuntimeTrace("app", "window initialize skipped: hwnd is null");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Host::RefreshBorderlessWindowDwmDecorations(m_session->GetHwnd());
|
|
|
|
|
m_chromeController->Reset();
|
|
|
|
|
m_chromeController->SetWindowDpi(QueryWindowDpi(m_session->GetHwnd()));
|
2026-04-25 18:20:17 +08:00
|
|
|
m_chromeController->InitializeWindowChrome(*this);
|
2026-04-25 16:46:01 +08:00
|
|
|
m_runtime->SetDpiScale(GetDpiScale());
|
|
|
|
|
|
|
|
|
|
std::ostringstream dpiTrace = {};
|
|
|
|
|
dpiTrace << "initial dpi=" << m_chromeController->GetWindowDpi()
|
|
|
|
|
<< " scale=" << GetDpiScale();
|
|
|
|
|
LogRuntimeTrace("window", dpiTrace.str());
|
|
|
|
|
|
|
|
|
|
MarkInitializing();
|
|
|
|
|
const bool initialized = m_runtime->Initialize(
|
|
|
|
|
m_session->GetHwnd(),
|
|
|
|
|
repoRoot,
|
|
|
|
|
editorContext,
|
|
|
|
|
captureRoot,
|
|
|
|
|
autoCaptureOnStartup);
|
|
|
|
|
if (initialized) {
|
|
|
|
|
MarkRunning();
|
|
|
|
|
} else {
|
|
|
|
|
m_session->MarkNativeAttached();
|
|
|
|
|
}
|
|
|
|
|
return initialized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::Shutdown() {
|
|
|
|
|
std::ostringstream trace = {};
|
|
|
|
|
trace << "EditorWindow::Shutdown begin windowId='" << GetWindowId()
|
|
|
|
|
<< "' hwnd=0x" << std::hex << std::uppercase
|
|
|
|
|
<< reinterpret_cast<std::uintptr_t>(GetHwnd())
|
|
|
|
|
<< std::dec
|
|
|
|
|
<< " primary=" << (IsPrimary() ? 1 : 0)
|
|
|
|
|
<< " lifecycle=" << GetEditorWindowLifecycleStateName(GetLifecycleState())
|
|
|
|
|
<< " runtimeReady=" << (m_runtime->IsReady() ? 1 : 0);
|
|
|
|
|
LogRuntimeTrace("window-close", trace.str());
|
|
|
|
|
ForceReleasePointerCapture();
|
|
|
|
|
|
|
|
|
|
if (m_runtime->IsReady()) {
|
|
|
|
|
m_runtime->Shutdown();
|
|
|
|
|
}
|
|
|
|
|
m_inputController->ClearPendingEvents();
|
|
|
|
|
m_chromeController->Reset();
|
|
|
|
|
LogRuntimeTrace(
|
|
|
|
|
"window-close",
|
|
|
|
|
"EditorWindow::Shutdown end windowId='" + std::string(GetWindowId()) + "'");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
const HWND hwnd = m_session->GetHwnd();
|
|
|
|
|
if (hwnd == nullptr || !IsWindow(hwnd)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RECT clientRect = {};
|
|
|
|
|
if (!GetClientRect(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<UINT>(width);
|
|
|
|
|
outHeight = static_cast<UINT>(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<float>(x)),
|
|
|
|
|
PixelsToDips(static_cast<float>(y)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIPoint EditorWindow::ConvertScreenPixelsToClientDips(const POINT& screenPoint) const {
|
|
|
|
|
POINT clientPoint = screenPoint;
|
|
|
|
|
if (const HWND hwnd = m_session->GetHwnd();
|
|
|
|
|
hwnd != nullptr) {
|
|
|
|
|
ScreenToClient(hwnd, &clientPoint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float dpiScale = m_chromeController->GetDpiScale(kBaseDpiScale);
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::TryResolveDockTabDragHotspot(
|
|
|
|
|
std::string_view nodeId,
|
|
|
|
|
std::string_view panelId,
|
|
|
|
|
const POINT& screenPoint,
|
|
|
|
|
POINT& outHotspot) const {
|
|
|
|
|
const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding();
|
|
|
|
|
if (dockHostBinding == nullptr) {
|
|
|
|
|
outHotspot = {};
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIPoint clientPointDips = ConvertScreenPixelsToClientDips(screenPoint);
|
|
|
|
|
UIPoint hotspotDips = {};
|
|
|
|
|
if (!dockHostBinding->TryResolveDockTabDragHotspot(
|
|
|
|
|
nodeId,
|
|
|
|
|
panelId,
|
|
|
|
|
clientPointDips,
|
|
|
|
|
hotspotDips)) {
|
|
|
|
|
outHotspot = {};
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float dpiScale = GetDpiScale();
|
|
|
|
|
outHotspot.x = static_cast<LONG>(std::lround(hotspotDips.x * dpiScale));
|
|
|
|
|
outHotspot.y = static_cast<LONG>(std::lround(hotspotDips.y * dpiScale));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::TryResolveDockTabDropTarget(
|
|
|
|
|
const POINT& screenPoint,
|
|
|
|
|
UIEditorDockHostTabDropTarget& outTarget) const {
|
|
|
|
|
const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding();
|
|
|
|
|
if (dockHostBinding == nullptr) {
|
|
|
|
|
outTarget = {};
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
outTarget = dockHostBinding->ResolveDockTabDropTarget(
|
|
|
|
|
ConvertScreenPixelsToClientDips(screenPoint));
|
|
|
|
|
return outTarget.valid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::OnResize(UINT width, UINT height) {
|
|
|
|
|
const bool matchedPresentedPrediction =
|
|
|
|
|
m_chromeController->ConsumePresentedPredictedClientPixelSizeMatch(width, height);
|
|
|
|
|
if (!matchedPresentedPrediction) {
|
|
|
|
|
m_chromeController->ClearPredictedClientPixelSize();
|
|
|
|
|
}
|
|
|
|
|
if (const HWND hwnd = m_session->GetHwnd();
|
|
|
|
|
hwnd != nullptr) {
|
|
|
|
|
Host::RefreshBorderlessWindowDwmDecorations(hwnd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (matchedPresentedPrediction) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ApplyWindowResize(width, height);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::OnEnterSizeMove() {
|
|
|
|
|
m_chromeController->BeginInteractiveResize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::OnExitSizeMove() {
|
|
|
|
|
m_chromeController->EndInteractiveResize();
|
|
|
|
|
m_chromeController->ClearPredictedClientPixelSize();
|
|
|
|
|
UINT width = 0u;
|
|
|
|
|
UINT height = 0u;
|
|
|
|
|
if (QueryCurrentClientPixelSize(width, height)) {
|
|
|
|
|
ApplyWindowResize(width, height);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
|
|
|
|
|
m_chromeController->SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi);
|
|
|
|
|
m_runtime->SetDpiScale(GetDpiScale());
|
|
|
|
|
if (const HWND hwnd = m_session->GetHwnd();
|
|
|
|
|
hwnd != nullptr) {
|
|
|
|
|
const LONG windowWidth = suggestedRect.right - suggestedRect.left;
|
|
|
|
|
const LONG windowHeight = suggestedRect.bottom - suggestedRect.top;
|
|
|
|
|
SetWindowPos(
|
|
|
|
|
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(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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
|
|
|
|
|
|
using namespace EditorWindowSupport;
|
|
|
|
|
using ::XCEngine::UI::UIDrawData;
|
|
|
|
|
using ::XCEngine::UI::UIDrawList;
|
|
|
|
|
using ::XCEngine::UI::UIInputEvent;
|
|
|
|
|
using ::XCEngine::UI::UIInputModifiers;
|
|
|
|
|
using ::XCEngine::UI::UIPointerButton;
|
|
|
|
|
using ::XCEngine::UI::UIRect;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2026-04-25 19:25:49 +08:00
|
|
|
std::optional<EditorWindowScreenPoint> QueryCursorScreenPoint() {
|
|
|
|
|
POINT screenPoint = {};
|
|
|
|
|
if (!GetCursorPos(&screenPoint)) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return EditorWindowScreenPoint{
|
|
|
|
|
.x = screenPoint.x,
|
|
|
|
|
.y = screenPoint.y,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 16:46:01 +08:00
|
|
|
std::uint8_t ButtonMask(UIPointerButton button) {
|
|
|
|
|
switch (button) {
|
|
|
|
|
case UIPointerButton::Left: return 1u << 0u;
|
|
|
|
|
case UIPointerButton::Right: return 1u << 1u;
|
|
|
|
|
case UIPointerButton::Middle: return 1u << 2u;
|
|
|
|
|
case UIPointerButton::X1: return 1u << 3u;
|
|
|
|
|
case UIPointerButton::X2: return 1u << 4u;
|
|
|
|
|
case UIPointerButton::None:
|
|
|
|
|
default:
|
|
|
|
|
return 0u;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::uint8_t ButtonMaskFromModifiers(const UIInputModifiers& modifiers) {
|
|
|
|
|
std::uint8_t mask = 0u;
|
|
|
|
|
if (modifiers.leftMouse) {
|
|
|
|
|
mask |= ButtonMask(UIPointerButton::Left);
|
|
|
|
|
}
|
|
|
|
|
if (modifiers.rightMouse) {
|
|
|
|
|
mask |= ButtonMask(UIPointerButton::Right);
|
|
|
|
|
}
|
|
|
|
|
if (modifiers.middleMouse) {
|
|
|
|
|
mask |= ButtonMask(UIPointerButton::Middle);
|
|
|
|
|
}
|
|
|
|
|
if (modifiers.x1Mouse) {
|
|
|
|
|
mask |= ButtonMask(UIPointerButton::X1);
|
|
|
|
|
}
|
|
|
|
|
if (modifiers.x2Mouse) {
|
|
|
|
|
mask |= ButtonMask(UIPointerButton::X2);
|
|
|
|
|
}
|
|
|
|
|
return mask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::uint8_t ResolveExpectedShellCaptureButtons(
|
|
|
|
|
const UIEditorShellInteractionState& shellState) {
|
|
|
|
|
std::uint8_t expectedButtons = 0u;
|
|
|
|
|
const auto& dockHostState =
|
|
|
|
|
shellState.workspaceInteractionState.dockHostInteractionState;
|
|
|
|
|
if (dockHostState.splitterDragState.active ||
|
|
|
|
|
!dockHostState.activeTabDragNodeId.empty()) {
|
|
|
|
|
expectedButtons |= ButtonMask(UIPointerButton::Left);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const auto& panelState :
|
|
|
|
|
shellState.workspaceInteractionState.composeState.panelStates) {
|
|
|
|
|
const auto& inputBridgeState = panelState.viewportShellState.inputBridgeState;
|
|
|
|
|
if (inputBridgeState.captured) {
|
|
|
|
|
expectedButtons |= ButtonMask(inputBridgeState.captureButton);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return expectedButtons;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
EditorWindowFrameTransferRequests EditorWindow::RenderFrame(
|
|
|
|
|
EditorContext& editorContext,
|
|
|
|
|
bool globalTabDragActive) {
|
|
|
|
|
if (!m_runtime->IsReady() || m_session->GetHwnd() == nullptr) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UINT pixelWidth = 0u;
|
|
|
|
|
UINT pixelHeight = 0u;
|
|
|
|
|
if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float width = PixelsToDips(static_cast<float>(pixelWidth));
|
|
|
|
|
const float height = PixelsToDips(static_cast<float>(pixelHeight));
|
|
|
|
|
const UIRect workspaceBounds = ResolveWorkspaceBounds(width, height);
|
|
|
|
|
|
|
|
|
|
UIDrawData drawData = {};
|
|
|
|
|
UIDrawList& backgroundDrawList = drawData.EmplaceDrawList("XCEditorWindow.Surface");
|
|
|
|
|
backgroundDrawList.AddFilledRect(
|
|
|
|
|
UIRect(0.0f, 0.0f, width, height),
|
|
|
|
|
kShellSurfaceColor);
|
|
|
|
|
|
|
|
|
|
EditorWindowFrameTransferRequests transferRequests = {};
|
|
|
|
|
if (editorContext.IsValid()) {
|
|
|
|
|
transferRequests =
|
|
|
|
|
RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawData);
|
|
|
|
|
} else {
|
|
|
|
|
UIDrawList& invalidDrawList = drawData.EmplaceDrawList("XCEditorWindow.Invalid");
|
|
|
|
|
m_frameOrchestrator->AppendInvalidFrame(editorContext, invalidDrawList);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome");
|
|
|
|
|
m_chromeController->AppendChrome(*this, windowChromeDrawList, width);
|
|
|
|
|
|
|
|
|
|
const Host::D3D12WindowRenderLoopPresentResult presentResult = m_runtime->Present(drawData);
|
|
|
|
|
if (!presentResult.warning.empty()) {
|
|
|
|
|
LogRuntimeTrace("present", presentResult.warning);
|
|
|
|
|
}
|
|
|
|
|
return transferRequests;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage(
|
|
|
|
|
EditorContext& editorContext,
|
|
|
|
|
bool globalTabDragActive) {
|
|
|
|
|
if (!m_runtime->IsReady() || m_session->GetHwnd() == nullptr) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PAINTSTRUCT paintStruct = {};
|
|
|
|
|
BeginPaint(m_session->GetHwnd(), &paintStruct);
|
|
|
|
|
const EditorWindowFrameTransferRequests transferRequests =
|
|
|
|
|
EditorWindowFrameDriver::DriveImmediateFrame(
|
|
|
|
|
*this,
|
|
|
|
|
editorContext,
|
|
|
|
|
globalTabDragActive);
|
|
|
|
|
EndPaint(m_session->GetHwnd(), &paintStruct);
|
|
|
|
|
return transferRequests;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::QueueCompletedImmediateFrame(
|
|
|
|
|
EditorWindowFrameTransferRequests transferRequests) {
|
|
|
|
|
m_session->QueueCompletedImmediateFrame(std::move(transferRequests));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::HasQueuedCompletedImmediateFrame() const {
|
|
|
|
|
return m_session->HasQueuedCompletedImmediateFrame();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorWindowFrameTransferRequests
|
|
|
|
|
EditorWindow::ConsumeQueuedCompletedImmediateFrameTransferRequests() {
|
|
|
|
|
return m_session->ConsumeQueuedCompletedImmediateFrameTransferRequests();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::RequestSkipNextSteadyStateFrame() {
|
|
|
|
|
m_chromeController->RequestSkipNextSteadyStateFrame();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::ConsumeSkipNextSteadyStateFrame() {
|
|
|
|
|
return m_chromeController->ConsumeSkipNextSteadyStateFrame();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const {
|
|
|
|
|
if (m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this)) {
|
|
|
|
|
return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float titleBarHeight = (std::min)(kBorderlessTitleBarHeightDips, clientHeightDips);
|
|
|
|
|
return UIRect(
|
|
|
|
|
0.0f,
|
|
|
|
|
titleBarHeight,
|
|
|
|
|
clientWidthDips,
|
|
|
|
|
(std::max)(0.0f, clientHeightDips - titleBarHeight));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame(
|
|
|
|
|
EditorContext& editorContext,
|
|
|
|
|
bool globalTabDragActive,
|
|
|
|
|
const UIRect& workspaceBounds,
|
|
|
|
|
UIDrawData& drawData) {
|
|
|
|
|
SyncShellCapturedPointerButtonsFromSystemState();
|
|
|
|
|
std::vector<UIInputEvent> frameEvents = m_inputController->TakePendingEvents();
|
|
|
|
|
const bool useDetachedTitleBarTabStrip =
|
|
|
|
|
m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this);
|
|
|
|
|
editorContext.AttachTextMeasurer(m_runtime->GetTextMeasurer());
|
|
|
|
|
const Host::D3D12WindowRenderLoopFrameContext frameContext = m_runtime->BeginFrame();
|
|
|
|
|
if (!frameContext.warning.empty()) {
|
|
|
|
|
LogRuntimeTrace("viewport", frameContext.warning);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const EditorWindowFrameTransferRequests transferRequests =
|
|
|
|
|
m_runtime->UpdateAndAppend(
|
|
|
|
|
EditorWindowContentFrameContext{
|
|
|
|
|
.editorContext = editorContext,
|
|
|
|
|
.bounds = workspaceBounds,
|
|
|
|
|
.inputEvents = frameEvents,
|
2026-04-25 19:25:49 +08:00
|
|
|
.cursorScreenPoint = QueryCursorScreenPoint(),
|
2026-04-25 16:46:01 +08:00
|
|
|
.captureStatusText = m_runtime->BuildCaptureStatusText(),
|
|
|
|
|
.primary = m_session->IsPrimary(),
|
|
|
|
|
.globalTabDragActive = globalTabDragActive,
|
|
|
|
|
.useDetachedTitleBarTabStrip = useDetachedTitleBarTabStrip,
|
|
|
|
|
},
|
|
|
|
|
drawData);
|
|
|
|
|
if (frameContext.canRenderViewports) {
|
|
|
|
|
m_runtime->RenderRequestedViewports(frameContext.renderContext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ApplyShellRuntimePointerCapture();
|
|
|
|
|
ApplyCurrentCursor();
|
|
|
|
|
return transferRequests;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() {
|
|
|
|
|
m_inputController->SyncInputModifiersFromSystemState();
|
|
|
|
|
|
|
|
|
|
const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons(
|
|
|
|
|
m_runtime->GetShellInteractionState());
|
|
|
|
|
if (expectedButtons == 0u ||
|
|
|
|
|
m_inputController->HasPendingPointerStateReconciliationEvent()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UIInputModifiers modifiers = m_inputController->GetCurrentModifiers();
|
|
|
|
|
if ((ButtonMaskFromModifiers(modifiers) & expectedButtons) == expectedButtons) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QueueSyntheticPointerStateSyncEvent(modifiers);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::ApplyShellRuntimePointerCapture() {
|
|
|
|
|
const EditorWindowInputFeedbackBinding* inputFeedbackBinding =
|
|
|
|
|
m_runtime->TryGetInputFeedbackBinding();
|
|
|
|
|
if (inputFeedbackBinding != nullptr &&
|
|
|
|
|
inputFeedbackBinding->HasShellInteractiveCapture()) {
|
|
|
|
|
AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (inputFeedbackBinding != nullptr &&
|
|
|
|
|
inputFeedbackBinding->HasHostedContentCapture()) {
|
|
|
|
|
AcquirePointerCapture(EditorWindowPointerCaptureOwner::HostedContent);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (OwnsPointerCapture(EditorWindowPointerCaptureOwner::Shell)) {
|
|
|
|
|
ReleasePointerCapture(EditorWindowPointerCaptureOwner::Shell);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (OwnsPointerCapture(EditorWindowPointerCaptureOwner::HostedContent)) {
|
|
|
|
|
ReleasePointerCapture(EditorWindowPointerCaptureOwner::HostedContent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
|
|
|
|
|
|
using ::XCEngine::UI::UIInputEventType;
|
|
|
|
|
using ::XCEngine::UI::UIPointerButton;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
bool IsScreenPointOverWindow(HWND hwnd, const POINT& screenPoint) {
|
|
|
|
|
if (hwnd == nullptr || !IsWindow(hwnd)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const HWND hitWindow = WindowFromPoint(screenPoint);
|
|
|
|
|
if (hitWindow == nullptr || GetAncestor(hitWindow, GA_ROOT) != hwnd) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RECT windowRect = {};
|
|
|
|
|
if (!GetWindowRect(hwnd, &windowRect)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return screenPoint.x >= windowRect.left && screenPoint.x < windowRect.right &&
|
|
|
|
|
screenPoint.y >= windowRect.top && screenPoint.y < windowRect.bottom;
|
|
|
|
|
}
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::ApplyCurrentCursor() const {
|
|
|
|
|
if (!HasInteractiveCaptureState() && !IsPointerInsideClientArea()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const HCURSOR cursor = LoadCursorW(nullptr, ResolveCurrentCursorResource());
|
|
|
|
|
if (cursor == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetCursor(cursor);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::HasInteractiveCaptureState() const {
|
|
|
|
|
const EditorWindowInputFeedbackBinding* inputFeedbackBinding =
|
|
|
|
|
m_runtime->TryGetInputFeedbackBinding();
|
|
|
|
|
return (inputFeedbackBinding != nullptr && inputFeedbackBinding->HasInteractiveCapture()) ||
|
|
|
|
|
m_chromeController->IsBorderlessWindowDragRestoreArmed() ||
|
|
|
|
|
m_chromeController->IsBorderlessResizeActive() ||
|
|
|
|
|
m_inputController->HasPointerCaptureOwner();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const {
|
|
|
|
|
return m_inputController->OwnsPointerCapture(owner);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) {
|
|
|
|
|
m_inputController->AcquirePointerCapture(m_session->GetHwnd(), owner);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) {
|
|
|
|
|
m_inputController->ReleasePointerCapture(m_session->GetHwnd(), owner);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::ForceReleasePointerCapture() {
|
|
|
|
|
m_inputController->ForceReleasePointerCapture(m_session->GetHwnd());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) {
|
|
|
|
|
const HWND hwnd = m_session->GetHwnd();
|
|
|
|
|
if (hwnd == nullptr ||
|
|
|
|
|
!IsWindow(hwnd) ||
|
|
|
|
|
GetCapture() == hwnd) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ::XCEngine::UI::UIPoint clientPoint = ConvertClientPixelsToDips(
|
|
|
|
|
GET_X_LPARAM(lParam),
|
|
|
|
|
GET_Y_LPARAM(lParam));
|
|
|
|
|
if (!ShouldStartImmediateUIEditorShellPointerCapture(
|
|
|
|
|
m_runtime->GetShellFrame(),
|
|
|
|
|
clientPoint)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::QueuePointerEvent(
|
|
|
|
|
UIInputEventType type,
|
|
|
|
|
UIPointerButton button,
|
|
|
|
|
WPARAM wParam,
|
|
|
|
|
LPARAM lParam,
|
|
|
|
|
bool doubleClick) {
|
|
|
|
|
m_inputController->QueuePointerEvent(
|
|
|
|
|
type,
|
|
|
|
|
button,
|
|
|
|
|
ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)),
|
|
|
|
|
wParam,
|
|
|
|
|
doubleClick);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::QueueSyntheticPointerStateSyncEvent(
|
|
|
|
|
const ::XCEngine::UI::UIInputModifiers& modifiers) {
|
|
|
|
|
const HWND hwnd = m_session->GetHwnd();
|
|
|
|
|
if (hwnd == nullptr || !IsWindow(hwnd)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
POINT screenPoint = {};
|
|
|
|
|
if (!GetCursorPos(&screenPoint)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!ScreenToClient(hwnd, &screenPoint)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_inputController->QueueSyntheticPointerStateSyncEvent(
|
|
|
|
|
ConvertClientPixelsToDips(screenPoint.x, screenPoint.y),
|
|
|
|
|
modifiers);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::QueuePointerLeaveEvent() {
|
|
|
|
|
::XCEngine::UI::UIPoint position = {};
|
|
|
|
|
if (const HWND hwnd = m_session->GetHwnd();
|
|
|
|
|
hwnd != nullptr) {
|
|
|
|
|
POINT clientPoint = {};
|
|
|
|
|
GetCursorPos(&clientPoint);
|
|
|
|
|
ScreenToClient(hwnd, &clientPoint);
|
|
|
|
|
position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y);
|
|
|
|
|
}
|
|
|
|
|
m_inputController->QueuePointerLeaveEvent(position);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) {
|
|
|
|
|
if (m_session->GetHwnd() == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
POINT screenPoint = {
|
|
|
|
|
GET_X_LPARAM(lParam),
|
|
|
|
|
GET_Y_LPARAM(lParam)
|
|
|
|
|
};
|
|
|
|
|
ScreenToClient(m_session->GetHwnd(), &screenPoint);
|
|
|
|
|
|
|
|
|
|
m_inputController->QueuePointerWheelEvent(
|
|
|
|
|
ConvertClientPixelsToDips(screenPoint.x, screenPoint.y),
|
|
|
|
|
wheelDelta,
|
|
|
|
|
wParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool EditorWindow::IsPointerInsideClientArea() const {
|
|
|
|
|
const HWND hwnd = m_session->GetHwnd();
|
|
|
|
|
if (hwnd == nullptr || !IsWindow(hwnd)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
POINT screenPoint = {};
|
|
|
|
|
if (!GetCursorPos(&screenPoint)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!IsScreenPointOverWindow(hwnd, screenPoint)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const LPARAM pointParam = MAKELPARAM(
|
|
|
|
|
static_cast<SHORT>(screenPoint.x),
|
|
|
|
|
static_cast<SHORT>(screenPoint.y));
|
|
|
|
|
return SendMessageW(hwnd, WM_NCHITTEST, 0, pointParam) == HTCLIENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LPCWSTR EditorWindow::ResolveCurrentCursorResource() const {
|
|
|
|
|
const EditorWindowInputFeedbackBinding* inputFeedbackBinding =
|
|
|
|
|
m_runtime->TryGetInputFeedbackBinding();
|
|
|
|
|
const Host::BorderlessWindowResizeEdge borderlessResizeEdge =
|
|
|
|
|
m_chromeController->IsBorderlessResizeActive()
|
|
|
|
|
? m_chromeController->GetBorderlessResizeEdge()
|
|
|
|
|
: m_chromeController->GetHoveredBorderlessResizeEdge();
|
|
|
|
|
if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) {
|
|
|
|
|
return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const EditorWindowContentCursorKind hostedContentCursorKind =
|
|
|
|
|
inputFeedbackBinding != nullptr
|
|
|
|
|
? inputFeedbackBinding->GetHostedContentCursorKind()
|
|
|
|
|
: EditorWindowContentCursorKind::Arrow;
|
|
|
|
|
switch (hostedContentCursorKind) {
|
|
|
|
|
case EditorWindowContentCursorKind::ResizeEW:
|
|
|
|
|
return IDC_SIZEWE;
|
|
|
|
|
case EditorWindowContentCursorKind::ResizeNS:
|
|
|
|
|
return IDC_SIZENS;
|
|
|
|
|
case EditorWindowContentCursorKind::Arrow:
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const EditorWindowContentCursorKind dockCursorKind =
|
|
|
|
|
inputFeedbackBinding != nullptr
|
|
|
|
|
? inputFeedbackBinding->GetDockCursorKind()
|
|
|
|
|
: EditorWindowContentCursorKind::Arrow;
|
|
|
|
|
switch (dockCursorKind) {
|
|
|
|
|
case EditorWindowContentCursorKind::ResizeEW:
|
|
|
|
|
return IDC_SIZEWE;
|
|
|
|
|
case EditorWindowContentCursorKind::ResizeNS:
|
|
|
|
|
return IDC_SIZENS;
|
|
|
|
|
case EditorWindowContentCursorKind::Arrow:
|
|
|
|
|
default:
|
|
|
|
|
return IDC_ARROW;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|
|
|
|
|
|
|
|
|
|
|