Fix editor host resize and dock splitter behavior
This commit is contained in:
@@ -36,8 +36,11 @@ using ::XCEngine::UI::UIRect;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost";
|
||||
constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor";
|
||||
constexpr const char* kWindowTitleText = "Main Scene * - Main.xx - XCEngine Editor";
|
||||
constexpr UINT kDefaultDpi = 96u;
|
||||
constexpr float kBaseDpiScale = 96.0f;
|
||||
constexpr float kBorderlessTitleBarHeightDips = 28.0f;
|
||||
constexpr float kBorderlessTitleBarFontSize = 12.0f;
|
||||
constexpr DWORD kBorderlessWindowStyle =
|
||||
WS_POPUP | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
|
||||
|
||||
@@ -436,7 +439,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
LogRuntimeTrace("app", "window creation failed");
|
||||
return false;
|
||||
}
|
||||
Host::EnableBorderlessWindowShadow(m_hwnd);
|
||||
Host::RefreshBorderlessWindowDwmDecorations(m_hwnd);
|
||||
m_hostRuntime.Reset();
|
||||
m_hostRuntime.SetWindowDpi(QueryWindowDpi(m_hwnd));
|
||||
m_renderer.SetDpiScale(GetDpiScale());
|
||||
@@ -534,6 +537,7 @@ void Application::RenderFrame() {
|
||||
}
|
||||
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");
|
||||
@@ -560,7 +564,7 @@ void Application::RenderFrame() {
|
||||
|
||||
m_editorWorkspace.Update(
|
||||
m_editorContext,
|
||||
UIRect(0.0f, 0.0f, width, height),
|
||||
workspaceBounds,
|
||||
frameEvents,
|
||||
BuildCaptureStatusText());
|
||||
const UIEditorShellInteractionFrame& shellFrame =
|
||||
@@ -639,6 +643,34 @@ bool Application::IsBorderlessWindowEnabled() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::IsBorderlessWindowMaximized() const {
|
||||
return m_hostRuntime.IsBorderlessWindowMaximized();
|
||||
}
|
||||
|
||||
bool Application::HandleBorderlessWindowSystemCommand(WPARAM wParam) {
|
||||
if (!IsBorderlessWindowEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (wParam & 0xFFF0u) {
|
||||
case SC_MAXIMIZE:
|
||||
ToggleBorderlessWindowMaximizeRestore();
|
||||
return true;
|
||||
case SC_RESTORE:
|
||||
if (!IsIconic(m_hwnd)) {
|
||||
ToggleBorderlessWindowMaximizeRestore();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const {
|
||||
return Host::HandleBorderlessWindowGetMinMaxInfo(m_hwnd, lParam);
|
||||
}
|
||||
|
||||
LRESULT Application::HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const {
|
||||
return Host::HandleBorderlessWindowNcCalcSize(
|
||||
m_hwnd,
|
||||
@@ -715,7 +747,7 @@ bool Application::ApplyCurrentCursor() const {
|
||||
}
|
||||
|
||||
Host::BorderlessWindowResizeEdge Application::HitTestBorderlessWindowResizeEdge(LPARAM lParam) const {
|
||||
if (!IsBorderlessWindowEnabled() || m_hwnd == nullptr || IsZoomed(m_hwnd)) {
|
||||
if (!IsBorderlessWindowEnabled() || m_hwnd == nullptr || IsBorderlessWindowMaximized()) {
|
||||
return Host::BorderlessWindowResizeEdge::None;
|
||||
}
|
||||
|
||||
@@ -857,23 +889,9 @@ void Application::ApplyBorderlessWindowResizeCursorHoverPriority() {
|
||||
|
||||
Host::BorderlessWindowChromeLayout Application::ResolveBorderlessWindowChromeLayout(
|
||||
float clientWidthDips) const {
|
||||
const auto& menuBarMetrics = ResolveUIEditorMenuBarMetrics();
|
||||
::XCEngine::UI::UIRect titleBarRect(0.0f, 0.0f, clientWidthDips, menuBarMetrics.barHeight);
|
||||
float leadingOccupiedRight = titleBarRect.x;
|
||||
|
||||
const UIEditorShellInteractionFrame& shellFrame = m_editorWorkspace.GetShellFrame();
|
||||
if (shellFrame.shellFrame.layout.menuBarRect.width > 0.0f &&
|
||||
shellFrame.shellFrame.layout.menuBarRect.height > 0.0f) {
|
||||
titleBarRect = shellFrame.shellFrame.layout.menuBarRect;
|
||||
}
|
||||
|
||||
const auto& buttonRects = shellFrame.shellFrame.layout.menuBarLayout.buttonRects;
|
||||
if (!buttonRects.empty()) {
|
||||
const auto& lastRect = buttonRects.back();
|
||||
leadingOccupiedRight = lastRect.x + lastRect.width;
|
||||
}
|
||||
|
||||
return Host::BuildBorderlessWindowChromeLayout(titleBarRect, leadingOccupiedRight);
|
||||
return Host::BuildBorderlessWindowChromeLayout(
|
||||
::XCEngine::UI::UIRect(0.0f, 0.0f, clientWidthDips, kBorderlessTitleBarHeightDips),
|
||||
0.0f);
|
||||
}
|
||||
|
||||
Host::BorderlessWindowChromeHitTarget Application::HitTestBorderlessWindowChrome(LPARAM lParam) const {
|
||||
@@ -938,6 +956,15 @@ bool Application::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) {
|
||||
return true;
|
||||
case Host::BorderlessWindowChromeHitTarget::DragRegion:
|
||||
if (m_hwnd != nullptr) {
|
||||
if (IsBorderlessWindowMaximized()) {
|
||||
POINT screenPoint = {};
|
||||
if (GetCursorPos(&screenPoint)) {
|
||||
m_hostRuntime.BeginBorderlessWindowDragRestore(screenPoint);
|
||||
SetCapture(m_hwnd);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ReleaseCapture();
|
||||
SendMessageW(m_hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
|
||||
}
|
||||
@@ -949,6 +976,11 @@ bool Application::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) {
|
||||
}
|
||||
|
||||
bool Application::HandleBorderlessWindowChromeButtonUp(LPARAM lParam) {
|
||||
if (m_hostRuntime.IsBorderlessWindowDragRestoreArmed()) {
|
||||
ClearBorderlessWindowChromeDragRestoreState();
|
||||
return true;
|
||||
}
|
||||
|
||||
const Host::BorderlessWindowChromeHitTarget pressedTarget =
|
||||
m_borderlessWindowChromeState.pressedTarget;
|
||||
if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton &&
|
||||
@@ -972,6 +1004,10 @@ bool Application::HandleBorderlessWindowChromeButtonUp(LPARAM lParam) {
|
||||
}
|
||||
|
||||
bool Application::HandleBorderlessWindowChromeDoubleClick(LPARAM lParam) {
|
||||
if (m_hostRuntime.IsBorderlessWindowDragRestoreArmed()) {
|
||||
ClearBorderlessWindowChromeDragRestoreState();
|
||||
}
|
||||
|
||||
if (HitTestBorderlessWindowChrome(lParam) != Host::BorderlessWindowChromeHitTarget::DragRegion) {
|
||||
return false;
|
||||
}
|
||||
@@ -980,6 +1016,85 @@ bool Application::HandleBorderlessWindowChromeDoubleClick(LPARAM lParam) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::HandleBorderlessWindowChromeDragRestorePointerMove() {
|
||||
if (!m_hostRuntime.IsBorderlessWindowDragRestoreArmed() || m_hwnd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
POINT currentScreenPoint = {};
|
||||
if (!GetCursorPos(¤tScreenPoint)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const POINT initialScreenPoint = m_hostRuntime.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_hostRuntime.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<float>(currentScreenPoint.x - currentRect.left) /
|
||||
static_cast<float>(currentWidth);
|
||||
const float clampedPointerRatio = (std::clamp)(pointerRatio, 0.0f, 1.0f);
|
||||
const int newLeft =
|
||||
(std::clamp)(
|
||||
currentScreenPoint.x - static_cast<int>(clampedPointerRatio * static_cast<float>(restoreWidth)),
|
||||
workAreaRect.left,
|
||||
workAreaRect.right - restoreWidth);
|
||||
const int titleBarHeightPixels =
|
||||
static_cast<int>(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_hostRuntime.SetBorderlessWindowMaximized(false);
|
||||
ApplyPredictedWindowRectTransition(targetRect);
|
||||
ClearBorderlessWindowChromeDragRestoreState();
|
||||
ReleaseCapture();
|
||||
SendMessageW(m_hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::ClearBorderlessWindowChromeDragRestoreState() {
|
||||
if (!m_hostRuntime.IsBorderlessWindowDragRestoreArmed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hostRuntime.EndBorderlessWindowDragRestore();
|
||||
if (GetCapture() == m_hwnd) {
|
||||
ReleaseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::ClearBorderlessWindowChromeState() {
|
||||
if (m_borderlessWindowChromeState.hoveredTarget == Host::BorderlessWindowChromeHitTarget::None &&
|
||||
m_borderlessWindowChromeState.pressedTarget == Host::BorderlessWindowChromeHitTarget::None) {
|
||||
@@ -999,11 +1114,29 @@ void Application::AppendBorderlessWindowChrome(
|
||||
|
||||
const Host::BorderlessWindowChromeLayout layout =
|
||||
ResolveBorderlessWindowChromeLayout(clientWidthDips);
|
||||
drawList.AddFilledRect(
|
||||
layout.titleBarRect,
|
||||
UIColor(0.97f, 0.97f, 0.97f, 1.0f));
|
||||
drawList.AddLine(
|
||||
UIPoint(layout.titleBarRect.x, layout.titleBarRect.y + layout.titleBarRect.height),
|
||||
UIPoint(
|
||||
layout.titleBarRect.x + layout.titleBarRect.width,
|
||||
layout.titleBarRect.y + layout.titleBarRect.height),
|
||||
UIColor(0.78f, 0.78f, 0.78f, 1.0f),
|
||||
1.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(
|
||||
12.0f,
|
||||
layout.titleBarRect.y +
|
||||
(std::max)(0.0f, (layout.titleBarRect.height - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)),
|
||||
kWindowTitleText,
|
||||
UIColor(0.12f, 0.12f, 0.12f, 1.0f),
|
||||
kBorderlessTitleBarFontSize);
|
||||
Host::AppendBorderlessWindowChrome(
|
||||
drawList,
|
||||
layout,
|
||||
m_borderlessWindowChromeState,
|
||||
m_hwnd != nullptr && IsZoomed(m_hwnd));
|
||||
IsBorderlessWindowMaximized());
|
||||
}
|
||||
|
||||
void Application::ExecuteBorderlessWindowChromeAction(
|
||||
@@ -1017,7 +1150,7 @@ void Application::ExecuteBorderlessWindowChromeAction(
|
||||
ShowWindow(m_hwnd, SW_MINIMIZE);
|
||||
break;
|
||||
case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
|
||||
ShowWindow(m_hwnd, IsZoomed(m_hwnd) ? SW_RESTORE : SW_MAXIMIZE);
|
||||
ToggleBorderlessWindowMaximizeRestore();
|
||||
break;
|
||||
case Host::BorderlessWindowChromeHitTarget::CloseButton:
|
||||
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
|
||||
@@ -1031,6 +1164,89 @@ void Application::ExecuteBorderlessWindowChromeAction(
|
||||
InvalidateHostWindow();
|
||||
}
|
||||
|
||||
bool Application::QueryCurrentWindowRect(RECT& outRect) const {
|
||||
outRect = {};
|
||||
return m_hwnd != nullptr && GetWindowRect(m_hwnd, &outRect) != FALSE;
|
||||
}
|
||||
|
||||
bool Application::QueryBorderlessWindowWorkAreaRect(RECT& outRect) const {
|
||||
outRect = {};
|
||||
if (m_hwnd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
if (monitor == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MONITORINFO monitorInfo = {};
|
||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||
if (!GetMonitorInfoW(monitor, &monitorInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRect = monitorInfo.rcWork;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::ApplyPredictedWindowRectTransition(const RECT& targetRect) {
|
||||
if (m_hwnd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int width = targetRect.right - targetRect.left;
|
||||
const int height = targetRect.bottom - targetRect.top;
|
||||
if (width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hostRuntime.SetPredictedClientPixelSize(
|
||||
static_cast<UINT>(width),
|
||||
static_cast<UINT>(height));
|
||||
ApplyWindowResize(
|
||||
static_cast<UINT>(width),
|
||||
static_cast<UINT>(height));
|
||||
RenderFrame();
|
||||
SetWindowPos(
|
||||
m_hwnd,
|
||||
nullptr,
|
||||
targetRect.left,
|
||||
targetRect.top,
|
||||
width,
|
||||
height,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
InvalidateHostWindow();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::ToggleBorderlessWindowMaximizeRestore() {
|
||||
if (m_hwnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsBorderlessWindowMaximized()) {
|
||||
RECT currentRect = {};
|
||||
RECT workAreaRect = {};
|
||||
if (!QueryCurrentWindowRect(currentRect) || !QueryBorderlessWindowWorkAreaRect(workAreaRect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hostRuntime.SetBorderlessWindowRestoreRect(currentRect);
|
||||
m_hostRuntime.SetBorderlessWindowMaximized(true);
|
||||
ApplyPredictedWindowRectTransition(workAreaRect);
|
||||
return;
|
||||
}
|
||||
|
||||
RECT restoreRect = {};
|
||||
if (!m_hostRuntime.TryGetBorderlessWindowRestoreRect(restoreRect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hostRuntime.SetBorderlessWindowMaximized(false);
|
||||
ApplyPredictedWindowRectTransition(restoreRect);
|
||||
}
|
||||
|
||||
void Application::InvalidateHostWindow() const {
|
||||
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
@@ -1091,6 +1307,9 @@ std::string Application::DescribeInputEvents(
|
||||
|
||||
void Application::OnResize(UINT width, UINT height) {
|
||||
m_hostRuntime.ClearPredictedClientPixelSize();
|
||||
if (IsBorderlessWindowEnabled() && m_hwnd != nullptr) {
|
||||
Host::RefreshBorderlessWindowDwmDecorations(m_hwnd);
|
||||
}
|
||||
ApplyWindowResize(width, height);
|
||||
}
|
||||
|
||||
@@ -1160,6 +1379,19 @@ bool Application::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight)
|
||||
return QueryCurrentClientPixelSize(outWidth, outHeight);
|
||||
}
|
||||
|
||||
UIRect Application::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const {
|
||||
if (!IsBorderlessWindowEnabled()) {
|
||||
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));
|
||||
}
|
||||
|
||||
void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
|
||||
m_hostRuntime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi);
|
||||
m_renderer.SetDpiScale(GetDpiScale());
|
||||
@@ -1179,7 +1411,7 @@ void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
|
||||
if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) {
|
||||
ApplyWindowResize(clientWidth, clientHeight);
|
||||
}
|
||||
Host::EnableBorderlessWindowShadow(m_hwnd);
|
||||
Host::RefreshBorderlessWindowDwmDecorations(m_hwnd);
|
||||
}
|
||||
|
||||
std::ostringstream trace = {};
|
||||
@@ -1209,7 +1441,8 @@ void Application::ApplyHostedContentCaptureRequests() {
|
||||
}
|
||||
|
||||
bool Application::HasInteractiveCaptureState() const {
|
||||
return m_editorWorkspace.HasInteractiveCapture();
|
||||
return m_editorWorkspace.HasInteractiveCapture() ||
|
||||
m_hostRuntime.IsBorderlessWindowDragRestoreArmed();
|
||||
}
|
||||
|
||||
void Application::QueuePointerEvent(
|
||||
@@ -1330,6 +1563,9 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
|
||||
if (application->HandleBorderlessWindowResizePointerMove()) {
|
||||
return 0;
|
||||
}
|
||||
if (application->HandleBorderlessWindowChromeDragRestorePointerMove()) {
|
||||
return 0;
|
||||
}
|
||||
const bool resizeHoverChanged =
|
||||
application->UpdateBorderlessWindowResizeHover(lParam);
|
||||
if (application->UpdateBorderlessWindowChromeHover(lParam)) {
|
||||
@@ -1370,6 +1606,7 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
|
||||
if (application != nullptr) {
|
||||
application->m_trackingMouseLeave = false;
|
||||
application->ClearBorderlessWindowResizeState();
|
||||
application->ClearBorderlessWindowChromeDragRestoreState();
|
||||
application->ClearBorderlessWindowChromeState();
|
||||
application->QueuePointerLeaveEvent();
|
||||
return 0;
|
||||
@@ -1440,12 +1677,14 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
|
||||
application->HasInteractiveCaptureState()) {
|
||||
application->QueueWindowFocusEvent(UIInputEventType::FocusLost);
|
||||
application->ForceClearBorderlessWindowResizeState();
|
||||
application->ClearBorderlessWindowChromeDragRestoreState();
|
||||
application->ClearBorderlessWindowChromeState();
|
||||
return 0;
|
||||
}
|
||||
if (application != nullptr &&
|
||||
reinterpret_cast<HWND>(lParam) != hwnd) {
|
||||
application->ForceClearBorderlessWindowResizeState();
|
||||
application->ClearBorderlessWindowChromeDragRestoreState();
|
||||
application->ClearBorderlessWindowChromeState();
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user