#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 #include #include #include #include #include #include "Composition/EditorShellPointerInteraction.h" #include #include #include #include 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(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(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(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()) , m_chromeController(std::make_unique()) , m_frameOrchestrator(std::make_unique()) , m_inputController(std::make_unique()) , m_runtime(std::make_unique( 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(width); outHeight = static_cast(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(x)), PixelsToDips(static_cast(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(clientPoint.x) / dpiScale : static_cast(clientPoint.x), dpiScale > 0.0f ? static_cast(clientPoint.y) / dpiScale : static_cast(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(std::lround(hotspotDips.x * dpiScale)); outHotspot.y = static_cast(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(pixelWidth)); const float height = PixelsToDips(static_cast(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 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(screenPoint.x), static_cast(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