#include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" #include #include namespace XCEngine::UI::Editor::App { using namespace EditorWindowSupport; bool EditorWindow::UpdateBorderlessWindowChromeHover(LPARAM lParam) { if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || m_chrome.runtime.IsBorderlessResizeActive()) { const bool changed = m_chrome.chromeState.hoveredTarget != Host::BorderlessWindowChromeHitTarget::None; m_chrome.chromeState.hoveredTarget = Host::BorderlessWindowChromeHitTarget::None; return changed; } const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam); const Host::BorderlessWindowChromeHitTarget buttonTarget = hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || hitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton ? hitTarget : Host::BorderlessWindowChromeHitTarget::None; if (m_chrome.chromeState.hoveredTarget == buttonTarget) { return false; } m_chrome.chromeState.hoveredTarget = buttonTarget; return true; } bool EditorWindow::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) { if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || m_chrome.runtime.IsBorderlessResizeActive()) { return false; } const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam); switch (hitTarget) { case Host::BorderlessWindowChromeHitTarget::MinimizeButton: case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: case Host::BorderlessWindowChromeHitTarget::CloseButton: m_chrome.chromeState.pressedTarget = hitTarget; if (m_window.hwnd != nullptr) { SetCapture(m_window.hwnd); } InvalidateHostWindow(); return true; case Host::BorderlessWindowChromeHitTarget::DragRegion: if (m_window.hwnd != nullptr) { if (IsBorderlessWindowMaximized()) { POINT screenPoint = {}; if (GetCursorPos(&screenPoint)) { m_chrome.runtime.BeginBorderlessWindowDragRestore(screenPoint); SetCapture(m_window.hwnd); return true; } } ReleaseCapture(); SendMessageW(m_window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); } return true; case Host::BorderlessWindowChromeHitTarget::None: default: return false; } } bool EditorWindow::HandleBorderlessWindowChromeButtonUp( EditorContext& editorContext, bool globalTabDragActive, LPARAM lParam) { if (m_chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { ClearBorderlessWindowChromeDragRestoreState(); return true; } const Host::BorderlessWindowChromeHitTarget pressedTarget = m_chrome.chromeState.pressedTarget; if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton && pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton && pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) { return false; } const Host::BorderlessWindowChromeHitTarget releasedTarget = HitTestBorderlessWindowChrome(lParam); m_chrome.chromeState.pressedTarget = Host::BorderlessWindowChromeHitTarget::None; if (GetCapture() == m_window.hwnd) { ReleaseCapture(); } InvalidateHostWindow(); if (pressedTarget == releasedTarget) { ExecuteBorderlessWindowChromeAction( editorContext, globalTabDragActive, pressedTarget); } return true; } bool EditorWindow::HandleBorderlessWindowChromeDoubleClick( EditorContext& editorContext, bool globalTabDragActive, LPARAM lParam) { if (m_chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { ClearBorderlessWindowChromeDragRestoreState(); } if (HitTestBorderlessWindowChrome(lParam) != Host::BorderlessWindowChromeHitTarget::DragRegion) { return false; } ExecuteBorderlessWindowChromeAction( editorContext, globalTabDragActive, Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton); return true; } bool EditorWindow::HandleBorderlessWindowChromeDragRestorePointerMove( EditorContext& editorContext, bool globalTabDragActive) { if (!m_chrome.runtime.IsBorderlessWindowDragRestoreArmed() || m_window.hwnd == nullptr) { return false; } POINT currentScreenPoint = {}; if (!GetCursorPos(¤tScreenPoint)) { return true; } const POINT initialScreenPoint = m_chrome.runtime.GetBorderlessWindowDragRestoreInitialScreenPoint(); const int dragThresholdX = (std::max)(GetSystemMetrics(SM_CXDRAG), 1); const int dragThresholdY = (std::max)(GetSystemMetrics(SM_CYDRAG), 1); const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x; const LONG deltaY = currentScreenPoint.y - initialScreenPoint.y; if (std::abs(deltaX) < dragThresholdX && std::abs(deltaY) < dragThresholdY) { return true; } RECT restoreRect = {}; RECT currentRect = {}; RECT workAreaRect = {}; if (!m_chrome.runtime.TryGetBorderlessWindowRestoreRect(restoreRect) || !QueryCurrentWindowRect(currentRect) || !QueryBorderlessWindowWorkAreaRect(workAreaRect)) { ClearBorderlessWindowChromeDragRestoreState(); return true; } const int restoreWidth = restoreRect.right - restoreRect.left; const int restoreHeight = restoreRect.bottom - restoreRect.top; const int currentWidth = currentRect.right - currentRect.left; if (restoreWidth <= 0 || restoreHeight <= 0 || currentWidth <= 0) { ClearBorderlessWindowChromeDragRestoreState(); return true; } const float pointerRatio = static_cast(currentScreenPoint.x - currentRect.left) / static_cast(currentWidth); const float clampedPointerRatio = (std::clamp)(pointerRatio, 0.0f, 1.0f); const int newLeft = (std::clamp)( currentScreenPoint.x - static_cast(clampedPointerRatio * static_cast(restoreWidth)), workAreaRect.left, workAreaRect.right - restoreWidth); const int titleBarHeightPixels = static_cast(kBorderlessTitleBarHeightDips * GetDpiScale()); const int newTop = (std::clamp)( currentScreenPoint.y - (std::max)(titleBarHeightPixels / 2, 1), workAreaRect.top, workAreaRect.bottom - restoreHeight); const RECT targetRect = { newLeft, newTop, newLeft + restoreWidth, newTop + restoreHeight }; m_chrome.runtime.SetBorderlessWindowMaximized(false); ApplyPredictedWindowRectTransition( editorContext, globalTabDragActive, targetRect); ClearBorderlessWindowChromeDragRestoreState(); ReleaseCapture(); SendMessageW(m_window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); return true; } void EditorWindow::ClearBorderlessWindowChromeDragRestoreState() { if (!m_chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { return; } m_chrome.runtime.EndBorderlessWindowDragRestore(); if (GetCapture() == m_window.hwnd) { ReleaseCapture(); } } void EditorWindow::ClearBorderlessWindowChromeState() { if (m_chrome.chromeState.hoveredTarget == Host::BorderlessWindowChromeHitTarget::None && m_chrome.chromeState.pressedTarget == Host::BorderlessWindowChromeHitTarget::None) { return; } m_chrome.chromeState = {}; InvalidateHostWindow(); } void EditorWindow::ExecuteBorderlessWindowChromeAction( EditorContext& editorContext, bool globalTabDragActive, Host::BorderlessWindowChromeHitTarget target) { if (m_window.hwnd == nullptr) { return; } switch (target) { case Host::BorderlessWindowChromeHitTarget::MinimizeButton: ShowWindow(m_window.hwnd, SW_MINIMIZE); break; case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); break; case Host::BorderlessWindowChromeHitTarget::CloseButton: PostMessageW(m_window.hwnd, WM_CLOSE, 0, 0); break; case Host::BorderlessWindowChromeHitTarget::DragRegion: case Host::BorderlessWindowChromeHitTarget::None: default: break; } InvalidateHostWindow(); } } // namespace XCEngine::UI::Editor::App