Files
XCEngine/new_editor/app/Platform/Win32/EditorWindow.cpp

1057 lines
32 KiB
C++
Raw Normal View History

#include "Platform/Win32/EditorWindow.h"
#include "Bootstrap/EditorResources.h"
#include "Platform/Win32/EditorWindowChromeController.h"
#include "Platform/Win32/EditorWindowSupport.h"
#include "Platform/Win32/EditorWindowFrameOrchestrator.h"
#include "Platform/Win32/EditorWindowInputController.h"
#include "Platform/Win32/EditorWindowState.h"
#include "Platform/Win32/EditorWindowRuntimeController.h"
#include "Composition/EditorContext.h"
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <XCEngine/UI/Types.h>
#include <algorithm>
#include <sstream>
#include <windowsx.h>
#include "Composition/EditorShellPointerInteraction.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() {
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::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP");
}
} // 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,
bool primary,
UIEditorWorkspaceController workspaceController)
: m_state(std::make_unique<EditorWindowState>())
, 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(workspaceController))) {
m_state->window.windowId = std::move(windowId);
m_state->window.title = std::move(title);
m_state->window.primary = primary;
UpdateCachedTitleText();
}
EditorWindow::~EditorWindow() = default;
std::string_view EditorWindow::GetWindowId() const {
return m_state->window.windowId;
}
HWND EditorWindow::GetHwnd() const {
return m_state->window.hwnd;
}
bool EditorWindow::HasHwnd() const {
return m_state->window.hwnd != nullptr;
}
bool EditorWindow::IsPrimary() const {
return m_state->window.primary;
}
bool EditorWindow::IsClosing() const {
return m_state->window.closing;
}
bool EditorWindow::IsRenderReady() const {
return m_runtime->IsReady();
}
bool EditorWindow::IsTrackingMouseLeave() const {
return m_inputController->IsTrackingMouseLeave();
}
bool EditorWindow::HasHoveredBorderlessResizeEdge() const {
return m_chromeController->GetHoveredBorderlessResizeEdge() !=
Host::BorderlessWindowResizeEdge::None;
}
const std::wstring& EditorWindow::GetTitle() const {
return m_state->window.title;
}
const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const {
return m_runtime->GetWorkspaceController();
}
UIEditorWorkspaceController& EditorWindow::GetMutableWorkspaceController() {
return m_runtime->GetMutableWorkspaceController();
}
const EditorShellRuntime& EditorWindow::GetShellRuntime() const {
return m_runtime->GetShellRuntime();
}
EditorShellRuntime& EditorWindow::GetShellRuntime() {
return m_runtime->GetShellRuntime();
}
const UIEditorShellInteractionFrame& EditorWindow::GetShellFrame() const {
return m_runtime->GetShellFrame();
}
const UIEditorShellInteractionState& EditorWindow::GetShellInteractionState() const {
return m_runtime->GetShellInteractionState();
}
void EditorWindow::SetExternalDockHostDropPreview(
const Widgets::UIEditorDockHostDropPreviewState& preview) {
m_runtime->SetExternalDockHostDropPreview(preview);
}
void EditorWindow::ClearExternalDockHostDropPreview() {
m_runtime->ClearExternalDockHostDropPreview();
}
void EditorWindow::AttachHwnd(HWND hwnd) {
m_state->window.hwnd = hwnd;
m_state->window.closing = false;
}
void EditorWindow::MarkDestroyed() {
m_state->window.hwnd = nullptr;
m_state->window.closing = false;
m_inputController->ResetWindowState();
}
void EditorWindow::MarkClosing() {
m_state->window.closing = true;
}
void EditorWindow::ClearClosing() {
m_state->window.closing = false;
}
void EditorWindow::SetTrackingMouseLeave(bool trackingMouseLeave) {
m_inputController->SetTrackingMouseLeave(trackingMouseLeave);
}
void EditorWindow::SetTitle(std::wstring title) {
m_state->window.title = std::move(title);
UpdateCachedTitleText();
}
void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) {
m_runtime->ReplaceWorkspaceController(std::move(workspaceController));
}
void EditorWindow::InvalidateHostWindow() const {
if (m_state->window.hwnd != nullptr && IsWindow(m_state->window.hwnd)) {
InvalidateRect(m_state->window.hwnd, nullptr, FALSE);
}
}
bool EditorWindow::Initialize(
const std::filesystem::path& repoRoot,
EditorContext& editorContext,
const std::filesystem::path& captureRoot,
bool autoCaptureOnStartup) {
if (m_state->window.hwnd == nullptr) {
LogRuntimeTrace("app", "window initialize skipped: hwnd is null");
return false;
}
Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd);
m_chromeController->Reset();
m_chromeController->SetWindowDpi(QueryWindowDpi(m_state->window.hwnd));
m_runtime->SetDpiScale(GetDpiScale());
std::ostringstream dpiTrace = {};
dpiTrace << "initial dpi=" << m_chromeController->GetWindowDpi()
<< " scale=" << GetDpiScale();
LogRuntimeTrace("window", dpiTrace.str());
return m_runtime->Initialize(
m_state->window.hwnd,
repoRoot,
editorContext,
captureRoot,
autoCaptureOnStartup);
}
void EditorWindow::Shutdown() {
ForceReleasePointerCapture();
m_runtime->Shutdown();
m_inputController->ClearPendingEvents();
m_chromeController->Reset();
}
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;
if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) {
return false;
}
RECT clientRect = {};
if (!GetClientRect(m_state->window.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 (m_state->window.hwnd != nullptr) {
ScreenToClient(m_state->window.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 UIPoint clientPointDips = ConvertScreenPixelsToClientDips(screenPoint);
UIPoint hotspotDips = {};
if (!m_runtime->GetShellRuntime().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 {
outTarget = m_runtime->GetShellRuntime().ResolveDockTabDropTarget(
ConvertScreenPixelsToClientDips(screenPoint));
return outTarget.valid;
}
void EditorWindow::OnResize(UINT width, UINT height) {
bool matchesPredictedClientSize = false;
UINT predictedWidth = 0u;
UINT predictedHeight = 0u;
if (m_chromeController->TryGetPredictedClientPixelSize(
predictedWidth,
predictedHeight)) {
matchesPredictedClientSize =
predictedWidth == width &&
predictedHeight == height;
}
m_chromeController->ClearPredictedClientPixelSize();
if (IsBorderlessWindowEnabled() && m_state->window.hwnd != nullptr) {
Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd);
}
if (!matchesPredictedClientSize) {
ApplyWindowResize(width, height);
}
}
void EditorWindow::OnEnterSizeMove() {
m_chromeController->BeginInteractiveResize();
}
void EditorWindow::OnExitSizeMove() {
m_chromeController->EndInteractiveResize();
m_chromeController->ClearPredictedClientPixelSize();
UINT width = 0u;
UINT height = 0u;
if (QueryCurrentClientPixelSize(width, height)) {
ApplyWindowResize(width, height);
}
}
void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
m_chromeController->SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi);
m_runtime->SetDpiScale(GetDpiScale());
if (m_state->window.hwnd != nullptr) {
const LONG windowWidth = suggestedRect.right - suggestedRect.left;
const LONG windowHeight = suggestedRect.bottom - suggestedRect.top;
SetWindowPos(
m_state->window.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(m_state->window.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;
}
void EditorWindow::UpdateCachedTitleText() {
m_state->window.titleText = WideToUtf8(m_state->window.title);
}
} // namespace XCEngine::UI::Editor::App
namespace XCEngine::UI::Editor::App {
bool EditorWindow::IsBorderlessWindowEnabled() const {
return true;
}
bool EditorWindow::IsBorderlessWindowMaximized() const {
return m_chromeController->IsBorderlessWindowMaximized();
}
bool EditorWindow::HandleBorderlessWindowSystemCommand(
EditorContext& editorContext,
bool globalTabDragActive,
WPARAM wParam) {
return m_chromeController->HandleSystemCommand(
*this,
editorContext,
globalTabDragActive,
wParam);
}
bool EditorWindow::HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const {
return m_chromeController->HandleGetMinMaxInfo(*this, lParam);
}
LRESULT EditorWindow::HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const {
return m_chromeController->HandleNcCalcSize(*this, wParam, lParam);
}
bool EditorWindow::QueryCurrentWindowRect(RECT& outRect) const {
return m_chromeController->QueryCurrentWindowRect(*this, outRect);
}
bool EditorWindow::QueryBorderlessWindowWorkAreaRect(RECT& outRect) const {
return m_chromeController->QueryBorderlessWindowWorkAreaRect(*this, outRect);
}
bool EditorWindow::ApplyPredictedWindowRectTransition(
EditorContext& editorContext,
bool globalTabDragActive,
const RECT& targetRect) {
return m_chromeController->ApplyPredictedWindowRectTransition(
*this,
editorContext,
globalTabDragActive,
targetRect);
}
void EditorWindow::ToggleBorderlessWindowMaximizeRestore(
EditorContext& editorContext,
bool globalTabDragActive) {
m_chromeController->ToggleMaximizeRestore(*this, editorContext, globalTabDragActive);
}
} // namespace XCEngine::UI::Editor::App
namespace XCEngine::UI::Editor::App {
using ::XCEngine::UI::UIRect;
bool EditorWindow::UpdateBorderlessWindowResizeHover(LPARAM lParam) {
return m_chromeController->UpdateResizeHover(*this, lParam);
}
bool EditorWindow::HandleBorderlessWindowResizeButtonDown(LPARAM lParam) {
return m_chromeController->HandleResizeButtonDown(*this, lParam);
}
bool EditorWindow::HandleBorderlessWindowResizeButtonUp() {
return m_chromeController->HandleResizeButtonUp(*this);
}
bool EditorWindow::HandleBorderlessWindowResizePointerMove(
EditorContext& editorContext,
bool globalTabDragActive) {
return m_chromeController->HandleResizePointerMove(
*this,
editorContext,
globalTabDragActive);
}
void EditorWindow::ClearBorderlessWindowResizeState() {
m_chromeController->ClearResizeState(*this);
}
void EditorWindow::ForceClearBorderlessWindowResizeState() {
m_chromeController->ForceClearResizeState(*this);
}
Host::BorderlessWindowResizeEdge EditorWindow::HitTestBorderlessWindowResizeEdge(
LPARAM lParam) const {
return m_chromeController->HitTestResizeEdge(*this, lParam);
}
void EditorWindow::ApplyBorderlessWindowResizeCursorHoverPriority() {
m_chromeController->ApplyResizeCursorHoverPriority();
}
} // 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 {
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 EditorShellRuntime& shellRuntime) {
std::uint8_t expectedButtons = 0u;
const auto& shellState = shellRuntime.GetShellInteractionState();
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_state->window.hwnd == 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& drawList = drawData.EmplaceDrawList("XCEditorShell");
drawList.AddFilledRect(
UIRect(0.0f, 0.0f, width, height),
kShellSurfaceColor);
EditorWindowFrameTransferRequests transferRequests = {};
if (editorContext.IsValid()) {
transferRequests =
RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawList);
} else {
m_frameOrchestrator->AppendInvalidFrame(editorContext, drawList);
}
AppendBorderlessWindowChrome(drawList, width);
const Host::D3D12WindowRenderLoopPresentResult presentResult = m_runtime->Present(drawData);
if (!presentResult.warning.empty()) {
LogRuntimeTrace("present", presentResult.warning);
}
m_runtime->CaptureIfRequested(
drawData,
pixelWidth,
pixelHeight,
presentResult.framePresented);
return transferRequests;
}
EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage(
EditorContext& editorContext,
bool globalTabDragActive) {
if (!m_runtime->IsReady() || m_state->window.hwnd == nullptr) {
return {};
}
PAINTSTRUCT paintStruct = {};
BeginPaint(m_state->window.hwnd, &paintStruct);
const EditorWindowFrameTransferRequests transferRequests =
RenderFrame(editorContext, globalTabDragActive);
EndPaint(m_state->window.hwnd, &paintStruct);
return transferRequests;
}
UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const {
if (!IsBorderlessWindowEnabled()) {
return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips);
}
if (ShouldUseDetachedTitleBarTabStrip()) {
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,
UIDrawList& drawList) {
SyncShellCapturedPointerButtonsFromSystemState();
std::vector<UIInputEvent> frameEvents = m_inputController->TakePendingEvents();
const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip();
const EditorWindowFrameTransferRequests transferRequests =
m_frameOrchestrator->UpdateAndAppend(
editorContext,
*m_runtime,
workspaceBounds,
frameEvents,
m_runtime->BuildCaptureStatusText(),
m_state->window.primary,
globalTabDragActive,
useDetachedTitleBarTabStrip,
drawList);
ApplyShellRuntimePointerCapture();
ApplyCurrentCursor();
return transferRequests;
}
void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() {
m_inputController->SyncInputModifiersFromSystemState();
const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons(
m_runtime->GetShellRuntime());
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 EditorShellPointerOwner owner = m_runtime->GetShellRuntime().GetPointerOwner();
if (IsShellPointerOwner(owner)) {
AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell);
return;
}
if (IsHostedContentPointerOwner(owner)) {
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::UIInputEvent;
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 {
return m_runtime->GetShellRuntime().HasInteractiveCapture() ||
m_chromeController->IsBorderlessWindowDragRestoreArmed() ||
m_chromeController->IsBorderlessResizeActive() ||
m_inputController->HasPointerCaptureOwner();
}
EditorWindowPointerCaptureOwner EditorWindow::GetPointerCaptureOwner() const {
return m_inputController->GetPointerCaptureOwner();
}
bool EditorWindow::OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const {
return m_inputController->OwnsPointerCapture(owner);
}
void EditorWindow::AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) {
m_inputController->AcquirePointerCapture(m_state->window.hwnd, owner);
}
void EditorWindow::ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) {
m_inputController->ReleasePointerCapture(m_state->window.hwnd, owner);
}
void EditorWindow::ForceReleasePointerCapture() {
m_inputController->ForceReleasePointerCapture(m_state->window.hwnd);
}
void EditorWindow::ClearPointerCaptureOwner() {
m_inputController->ClearPointerCaptureOwner();
}
void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) {
if (m_state->window.hwnd == nullptr ||
!IsWindow(m_state->window.hwnd) ||
GetCapture() == m_state->window.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) {
UIInputEvent event = {};
m_inputController->QueuePointerEvent(
type,
button,
ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)),
wParam,
doubleClick);
}
void EditorWindow::QueueSyntheticPointerStateSyncEvent(
const ::XCEngine::UI::UIInputModifiers& modifiers) {
if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) {
return;
}
POINT screenPoint = {};
if (!GetCursorPos(&screenPoint)) {
return;
}
if (!ScreenToClient(m_state->window.hwnd, &screenPoint)) {
return;
}
m_inputController->QueueSyntheticPointerStateSyncEvent(
ConvertClientPixelsToDips(screenPoint.x, screenPoint.y),
modifiers);
}
void EditorWindow::QueuePointerLeaveEvent() {
::XCEngine::UI::UIPoint position = {};
if (m_state->window.hwnd != nullptr) {
POINT clientPoint = {};
GetCursorPos(&clientPoint);
ScreenToClient(m_state->window.hwnd, &clientPoint);
position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y);
}
m_inputController->QueuePointerLeaveEvent(position);
}
void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) {
if (m_state->window.hwnd == nullptr) {
return;
}
POINT screenPoint = {
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam)
};
ScreenToClient(m_state->window.hwnd, &screenPoint);
m_inputController->QueuePointerWheelEvent(
ConvertClientPixelsToDips(screenPoint.x, screenPoint.y),
wheelDelta,
wParam);
}
void EditorWindow::QueueKeyEvent(UIInputEventType type, WPARAM wParam, LPARAM lParam) {
m_inputController->QueueKeyEvent(type, wParam, lParam);
}
void EditorWindow::QueueCharacterEvent(WPARAM wParam, LPARAM) {
m_inputController->QueueCharacterEvent(wParam);
}
void EditorWindow::QueueWindowFocusEvent(UIInputEventType type) {
m_inputController->QueueWindowFocusEvent(type);
}
void EditorWindow::SyncInputModifiersFromSystemState() {
m_inputController->SyncInputModifiersFromSystemState();
}
void EditorWindow::ResetInputModifiers() {
m_inputController->ResetInputModifiers();
}
void EditorWindow::RequestManualScreenshot() {
m_runtime->RequestManualScreenshot("manual_f12");
}
bool EditorWindow::IsPointerInsideClientArea() const {
if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) {
return false;
}
POINT screenPoint = {};
if (!GetCursorPos(&screenPoint)) {
return false;
}
if (!IsScreenPointOverWindow(m_state->window.hwnd, screenPoint)) {
return false;
}
const LPARAM pointParam = MAKELPARAM(
static_cast<SHORT>(screenPoint.x),
static_cast<SHORT>(screenPoint.y));
return SendMessageW(m_state->window.hwnd, WM_NCHITTEST, 0, pointParam) == HTCLIENT;
}
LPCWSTR EditorWindow::ResolveCurrentCursorResource() const {
const Host::BorderlessWindowResizeEdge borderlessResizeEdge =
m_chromeController->IsBorderlessResizeActive()
? m_chromeController->GetBorderlessResizeEdge()
: m_chromeController->GetHoveredBorderlessResizeEdge();
if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) {
return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge);
}
switch (m_runtime->GetShellRuntime().GetHostedContentCursorKind()) {
case ProjectPanel::CursorKind::ResizeEW:
return IDC_SIZEWE;
case ProjectPanel::CursorKind::Arrow:
default:
break;
}
switch (m_runtime->GetShellRuntime().GetDockCursorKind()) {
case Widgets::UIEditorDockHostCursorKind::ResizeEW:
return IDC_SIZEWE;
case Widgets::UIEditorDockHostCursorKind::ResizeNS:
return IDC_SIZENS;
case Widgets::UIEditorDockHostCursorKind::Arrow:
default:
return IDC_ARROW;
}
}
} // namespace XCEngine::UI::Editor::App
namespace XCEngine::UI::Editor::App {
using namespace EditorWindowSupport;
bool EditorWindow::UpdateBorderlessWindowChromeHover(LPARAM lParam) {
return m_chromeController->UpdateChromeHover(*this, lParam);
}
bool EditorWindow::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) {
return m_chromeController->HandleChromeButtonDown(*this, lParam);
}
bool EditorWindow::HandleBorderlessWindowChromeButtonUp(
EditorContext& editorContext,
bool globalTabDragActive,
LPARAM lParam) {
return m_chromeController->HandleChromeButtonUp(
*this,
editorContext,
globalTabDragActive,
lParam);
}
bool EditorWindow::HandleBorderlessWindowChromeDoubleClick(
EditorContext& editorContext,
bool globalTabDragActive,
LPARAM lParam) {
return m_chromeController->HandleChromeDoubleClick(
*this,
editorContext,
globalTabDragActive,
lParam);
}
bool EditorWindow::HandleBorderlessWindowChromeDragRestorePointerMove(
EditorContext& editorContext,
bool globalTabDragActive) {
return m_chromeController->HandleChromeDragRestorePointerMove(
*this,
editorContext,
globalTabDragActive);
}
void EditorWindow::ClearBorderlessWindowChromeDragRestoreState() {
m_chromeController->ClearChromeDragRestoreState(*this);
}
void EditorWindow::ClearBorderlessWindowChromeState() {
m_chromeController->ClearChromeState(*this);
}
void EditorWindow::ExecuteBorderlessWindowChromeAction(
EditorContext& editorContext,
bool globalTabDragActive,
Host::BorderlessWindowChromeHitTarget target) {
m_chromeController->ExecuteChromeAction(*this, editorContext, globalTabDragActive, target);
}
} // namespace XCEngine::UI::Editor::App
namespace XCEngine::UI::Editor::App {
bool EditorWindow::ShouldUseDetachedTitleBarTabStrip() const {
return m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this);
}
Host::BorderlessWindowChromeHitTarget EditorWindow::HitTestBorderlessWindowChrome(
LPARAM lParam) const {
return m_chromeController->HitTestChrome(*this, lParam);
}
Host::BorderlessWindowChromeLayout EditorWindow::ResolveBorderlessWindowChromeLayout(
float clientWidthDips) const {
return m_chromeController->ResolveChromeLayout(*this, clientWidthDips);
}
void EditorWindow::AppendBorderlessWindowChrome(
::XCEngine::UI::UIDrawList& drawList,
float clientWidthDips) const {
m_chromeController->AppendChrome(*this, drawList, clientWidthDips);
}
} // namespace XCEngine::UI::Editor::App