Fix borderless host resize presentation ordering

This commit is contained in:
2026-04-14 01:42:44 +08:00
parent 9064c2f5f2
commit ba91e0f5dd
6 changed files with 523 additions and 6 deletions

View File

@@ -125,6 +125,7 @@ target_link_libraries(XCUIEditorLib PUBLIC
add_library(XCUIEditorHost STATIC add_library(XCUIEditorHost STATIC
app/Host/AutoScreenshot.cpp app/Host/AutoScreenshot.cpp
app/Host/BorderlessWindowChrome.cpp app/Host/BorderlessWindowChrome.cpp
app/Host/BorderlessWindowFrame.cpp
app/Host/D3D12HostDevice.cpp app/Host/D3D12HostDevice.cpp
app/Host/D3D12ShaderResourceDescriptorAllocator.cpp app/Host/D3D12ShaderResourceDescriptorAllocator.cpp
app/Host/D3D12WindowInteropContext.cpp app/Host/D3D12WindowInteropContext.cpp

View File

@@ -527,12 +527,11 @@ void Application::RenderFrame() {
return; return;
} }
RECT clientRect = {}; UINT pixelWidth = 0u;
GetClientRect(m_hwnd, &clientRect); UINT pixelHeight = 0u;
const unsigned int pixelWidth = if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) {
static_cast<unsigned int>((std::max)(clientRect.right - clientRect.left, 1L)); return;
const unsigned int pixelHeight = }
static_cast<unsigned int>((std::max)(clientRect.bottom - clientRect.top, 1L));
const float width = PixelsToDips(static_cast<float>(pixelWidth)); const float width = PixelsToDips(static_cast<float>(pixelWidth));
const float height = PixelsToDips(static_cast<float>(pixelHeight)); const float height = PixelsToDips(static_cast<float>(pixelHeight));
@@ -674,6 +673,14 @@ bool Application::IsPointerInsideClientArea() const {
} }
LPCWSTR Application::ResolveCurrentCursorResource() const { LPCWSTR Application::ResolveCurrentCursorResource() const {
const Host::BorderlessWindowResizeEdge borderlessResizeEdge =
m_hostRuntime.IsBorderlessResizeActive()
? m_hostRuntime.GetBorderlessResizeEdge()
: m_hostRuntime.GetHoveredBorderlessResizeEdge();
if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) {
return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge);
}
switch (m_editorWorkspace.GetHostedContentCursorKind()) { switch (m_editorWorkspace.GetHostedContentCursorKind()) {
case App::ProductProjectPanel::CursorKind::ResizeEW: case App::ProductProjectPanel::CursorKind::ResizeEW:
return IDC_SIZEWE; return IDC_SIZEWE;
@@ -707,6 +714,147 @@ bool Application::ApplyCurrentCursor() const {
return true; return true;
} }
Host::BorderlessWindowResizeEdge Application::HitTestBorderlessWindowResizeEdge(LPARAM lParam) const {
if (!IsBorderlessWindowEnabled() || m_hwnd == nullptr || IsZoomed(m_hwnd)) {
return Host::BorderlessWindowResizeEdge::None;
}
RECT clientRect = {};
if (!GetClientRect(m_hwnd, &clientRect)) {
return Host::BorderlessWindowResizeEdge::None;
}
const float clientWidthDips =
PixelsToDips(static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L)));
const float clientHeightDips =
PixelsToDips(static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L)));
return Host::HitTestBorderlessWindowResizeEdge(
::XCEngine::UI::UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips),
ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
}
bool Application::UpdateBorderlessWindowResizeHover(LPARAM lParam) {
const Host::BorderlessWindowResizeEdge hoveredEdge = HitTestBorderlessWindowResizeEdge(lParam);
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() == hoveredEdge) {
return false;
}
m_hostRuntime.SetHoveredBorderlessResizeEdge(hoveredEdge);
ApplyBorderlessWindowResizeCursorHoverPriority();
return true;
}
bool Application::HandleBorderlessWindowResizeButtonDown(LPARAM lParam) {
const Host::BorderlessWindowResizeEdge edge = HitTestBorderlessWindowResizeEdge(lParam);
if (edge == Host::BorderlessWindowResizeEdge::None || m_hwnd == nullptr) {
return false;
}
POINT screenPoint = {};
if (!GetCursorPos(&screenPoint)) {
return false;
}
RECT windowRect = {};
if (!GetWindowRect(m_hwnd, &windowRect)) {
return false;
}
m_hostRuntime.BeginBorderlessResize(edge, screenPoint, windowRect);
SetCapture(m_hwnd);
InvalidateHostWindow();
return true;
}
bool Application::HandleBorderlessWindowResizeButtonUp() {
if (!m_hostRuntime.IsBorderlessResizeActive()) {
return false;
}
m_hostRuntime.EndBorderlessResize();
if (GetCapture() == m_hwnd) {
ReleaseCapture();
}
InvalidateHostWindow();
return true;
}
bool Application::HandleBorderlessWindowResizePointerMove() {
if (!m_hostRuntime.IsBorderlessResizeActive() || m_hwnd == nullptr) {
return false;
}
POINT currentScreenPoint = {};
if (!GetCursorPos(&currentScreenPoint)) {
return false;
}
RECT targetRect = Host::ComputeBorderlessWindowResizeRect(
m_hostRuntime.GetBorderlessResizeInitialWindowRect(),
m_hostRuntime.GetBorderlessResizeInitialScreenPoint(),
currentScreenPoint,
m_hostRuntime.GetBorderlessResizeEdge(),
640,
360);
const int width = targetRect.right - targetRect.left;
const int height = targetRect.bottom - targetRect.top;
if (width <= 0 || height <= 0) {
return true;
}
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);
return true;
}
void Application::ClearBorderlessWindowResizeState() {
if (m_hostRuntime.IsBorderlessResizeActive()) {
return;
}
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None) {
return;
}
m_hostRuntime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None);
InvalidateHostWindow();
}
void Application::ForceClearBorderlessWindowResizeState() {
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None &&
!m_hostRuntime.IsBorderlessResizeActive()) {
return;
}
m_hostRuntime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None);
m_hostRuntime.EndBorderlessResize();
if (GetCapture() == m_hwnd) {
ReleaseCapture();
}
InvalidateHostWindow();
}
void Application::ApplyBorderlessWindowResizeCursorHoverPriority() {
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
m_hostRuntime.IsBorderlessResizeActive()) {
m_borderlessWindowChromeState.hoveredTarget = Host::BorderlessWindowChromeHitTarget::None;
}
}
Host::BorderlessWindowChromeLayout Application::ResolveBorderlessWindowChromeLayout( Host::BorderlessWindowChromeLayout Application::ResolveBorderlessWindowChromeLayout(
float clientWidthDips) const { float clientWidthDips) const {
const auto& menuBarMetrics = ResolveUIEditorMenuBarMetrics(); const auto& menuBarMetrics = ResolveUIEditorMenuBarMetrics();
@@ -748,6 +896,14 @@ Host::BorderlessWindowChromeHitTarget Application::HitTestBorderlessWindowChrome
} }
bool Application::UpdateBorderlessWindowChromeHover(LPARAM lParam) { bool Application::UpdateBorderlessWindowChromeHover(LPARAM lParam) {
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
m_hostRuntime.IsBorderlessResizeActive()) {
const bool changed =
m_borderlessWindowChromeState.hoveredTarget != Host::BorderlessWindowChromeHitTarget::None;
m_borderlessWindowChromeState.hoveredTarget = Host::BorderlessWindowChromeHitTarget::None;
return changed;
}
const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam); const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam);
const Host::BorderlessWindowChromeHitTarget buttonTarget = const Host::BorderlessWindowChromeHitTarget buttonTarget =
hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton ||
@@ -764,6 +920,11 @@ bool Application::UpdateBorderlessWindowChromeHover(LPARAM lParam) {
} }
bool Application::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) { bool Application::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) {
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
m_hostRuntime.IsBorderlessResizeActive()) {
return false;
}
const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam); const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam);
switch (hitTarget) { switch (hitTarget) {
case Host::BorderlessWindowChromeHitTarget::MinimizeButton: case Host::BorderlessWindowChromeHitTarget::MinimizeButton:
@@ -929,6 +1090,7 @@ std::string Application::DescribeInputEvents(
} }
void Application::OnResize(UINT width, UINT height) { void Application::OnResize(UINT width, UINT height) {
m_hostRuntime.ClearPredictedClientPixelSize();
ApplyWindowResize(width, height); ApplyWindowResize(width, height);
} }
@@ -938,6 +1100,7 @@ void Application::OnEnterSizeMove() {
void Application::OnExitSizeMove() { void Application::OnExitSizeMove() {
m_hostRuntime.EndInteractiveResize(); m_hostRuntime.EndInteractiveResize();
m_hostRuntime.ClearPredictedClientPixelSize();
UINT width = 0u; UINT width = 0u;
UINT height = 0u; UINT height = 0u;
if (QueryCurrentClientPixelSize(width, height)) { if (QueryCurrentClientPixelSize(width, height)) {
@@ -989,6 +1152,14 @@ bool Application::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) c
return true; return true;
} }
bool Application::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const {
if (m_hostRuntime.TryGetPredictedClientPixelSize(outWidth, outHeight)) {
return true;
}
return QueryCurrentClientPixelSize(outWidth, outHeight);
}
void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
m_hostRuntime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); m_hostRuntime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi);
m_renderer.SetDpiScale(GetDpiScale()); m_renderer.SetDpiScale(GetDpiScale());
@@ -1156,9 +1327,17 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
switch (message) { switch (message) {
case WM_MOUSEMOVE: case WM_MOUSEMOVE:
if (application != nullptr) { if (application != nullptr) {
if (application->HandleBorderlessWindowResizePointerMove()) {
return 0;
}
const bool resizeHoverChanged =
application->UpdateBorderlessWindowResizeHover(lParam);
if (application->UpdateBorderlessWindowChromeHover(lParam)) { if (application->UpdateBorderlessWindowChromeHover(lParam)) {
application->InvalidateHostWindow(); application->InvalidateHostWindow();
} }
if (resizeHoverChanged) {
application->InvalidateHostWindow();
}
if (!application->m_trackingMouseLeave) { if (!application->m_trackingMouseLeave) {
TRACKMOUSEEVENT trackMouseEvent = {}; TRACKMOUSEEVENT trackMouseEvent = {};
trackMouseEvent.cbSize = sizeof(trackMouseEvent); trackMouseEvent.cbSize = sizeof(trackMouseEvent);
@@ -1168,6 +1347,10 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
application->m_trackingMouseLeave = true; application->m_trackingMouseLeave = true;
} }
} }
if (application->m_hostRuntime.GetHoveredBorderlessResizeEdge() !=
Host::BorderlessWindowResizeEdge::None) {
return 0;
}
const Host::BorderlessWindowChromeHitTarget chromeHitTarget = const Host::BorderlessWindowChromeHitTarget chromeHitTarget =
application->HitTestBorderlessWindowChrome(lParam); application->HitTestBorderlessWindowChrome(lParam);
if (chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || if (chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton ||
@@ -1186,6 +1369,7 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
case WM_MOUSELEAVE: case WM_MOUSELEAVE:
if (application != nullptr) { if (application != nullptr) {
application->m_trackingMouseLeave = false; application->m_trackingMouseLeave = false;
application->ClearBorderlessWindowResizeState();
application->ClearBorderlessWindowChromeState(); application->ClearBorderlessWindowChromeState();
application->QueuePointerLeaveEvent(); application->QueuePointerLeaveEvent();
return 0; return 0;
@@ -1193,6 +1377,9 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
break; break;
case WM_LBUTTONDOWN: case WM_LBUTTONDOWN:
if (application != nullptr) { if (application != nullptr) {
if (application->HandleBorderlessWindowResizeButtonDown(lParam)) {
return 0;
}
if (application->HandleBorderlessWindowChromeButtonDown(lParam)) { if (application->HandleBorderlessWindowChromeButtonDown(lParam)) {
return 0; return 0;
} }
@@ -1207,6 +1394,9 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
break; break;
case WM_LBUTTONUP: case WM_LBUTTONUP:
if (application != nullptr) { if (application != nullptr) {
if (application->HandleBorderlessWindowResizeButtonUp()) {
return 0;
}
if (application->HandleBorderlessWindowChromeButtonUp(lParam)) { if (application->HandleBorderlessWindowChromeButtonUp(lParam)) {
return 0; return 0;
} }
@@ -1249,11 +1439,13 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
reinterpret_cast<HWND>(lParam) != hwnd && reinterpret_cast<HWND>(lParam) != hwnd &&
application->HasInteractiveCaptureState()) { application->HasInteractiveCaptureState()) {
application->QueueWindowFocusEvent(UIInputEventType::FocusLost); application->QueueWindowFocusEvent(UIInputEventType::FocusLost);
application->ForceClearBorderlessWindowResizeState();
application->ClearBorderlessWindowChromeState(); application->ClearBorderlessWindowChromeState();
return 0; return 0;
} }
if (application != nullptr && if (application != nullptr &&
reinterpret_cast<HWND>(lParam) != hwnd) { reinterpret_cast<HWND>(lParam) != hwnd) {
application->ForceClearBorderlessWindowResizeState();
application->ClearBorderlessWindowChromeState(); application->ClearBorderlessWindowChromeState();
} }
break; break;

View File

@@ -6,6 +6,7 @@
#include <Host/AutoScreenshot.h> #include <Host/AutoScreenshot.h>
#include <Host/BorderlessWindowChrome.h> #include <Host/BorderlessWindowChrome.h>
#include <Host/BorderlessWindowFrame.h>
#include <Host/D3D12WindowRenderer.h> #include <Host/D3D12WindowRenderer.h>
#include <Host/D3D12WindowRenderLoop.h> #include <Host/D3D12WindowRenderLoop.h>
#include <Host/HostRuntimeState.h> #include <Host/HostRuntimeState.h>
@@ -53,6 +54,7 @@ private:
void OnDpiChanged(UINT dpi, const RECT& suggestedRect); void OnDpiChanged(UINT dpi, const RECT& suggestedRect);
bool ApplyWindowResize(UINT width, UINT height); bool ApplyWindowResize(UINT width, UINT height);
bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const; bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const;
bool ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const;
bool IsPointerInsideClientArea() const; bool IsPointerInsideClientArea() const;
bool ApplyCurrentCursor() const; bool ApplyCurrentCursor() const;
LPCWSTR ResolveCurrentCursorResource() const; LPCWSTR ResolveCurrentCursorResource() const;
@@ -84,6 +86,14 @@ private:
bool HandleBorderlessWindowChromeButtonUp(LPARAM lParam); bool HandleBorderlessWindowChromeButtonUp(LPARAM lParam);
bool HandleBorderlessWindowChromeDoubleClick(LPARAM lParam); bool HandleBorderlessWindowChromeDoubleClick(LPARAM lParam);
void ClearBorderlessWindowChromeState(); void ClearBorderlessWindowChromeState();
Host::BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(LPARAM lParam) const;
bool UpdateBorderlessWindowResizeHover(LPARAM lParam);
bool HandleBorderlessWindowResizeButtonDown(LPARAM lParam);
bool HandleBorderlessWindowResizeButtonUp();
bool HandleBorderlessWindowResizePointerMove();
void ClearBorderlessWindowResizeState();
void ForceClearBorderlessWindowResizeState();
void ApplyBorderlessWindowResizeCursorHoverPriority();
void AppendBorderlessWindowChrome( void AppendBorderlessWindowChrome(
::XCEngine::UI::UIDrawList& drawList, ::XCEngine::UI::UIDrawList& drawList,
float clientWidthDips) const; float clientWidthDips) const;

View File

@@ -0,0 +1,176 @@
#include "BorderlessWindowFrame.h"
#include <algorithm>
namespace XCEngine::UI::Editor::Host {
namespace {
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) {
return rect.width > 0.0f &&
rect.height > 0.0f &&
point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
int ClampMinimum(int value, int minimum) {
return (std::max)(value, minimum);
}
} // namespace
BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(
const UIRect& clientRect,
const UIPoint& point,
const BorderlessWindowFrameMetrics& metrics) {
const float edge = (std::max)(metrics.resizeBorderThickness, 0.0f);
if (edge <= 0.0f || !IsPointInsideRect(clientRect, point)) {
return BorderlessWindowResizeEdge::None;
}
const bool left = point.x <= clientRect.x + edge;
const bool right = point.x >= clientRect.x + clientRect.width - edge;
const bool top = point.y <= clientRect.y + edge;
const bool bottom = point.y >= clientRect.y + clientRect.height - edge;
if (left && top) {
return BorderlessWindowResizeEdge::TopLeft;
}
if (right && top) {
return BorderlessWindowResizeEdge::TopRight;
}
if (left && bottom) {
return BorderlessWindowResizeEdge::BottomLeft;
}
if (right && bottom) {
return BorderlessWindowResizeEdge::BottomRight;
}
if (left) {
return BorderlessWindowResizeEdge::Left;
}
if (right) {
return BorderlessWindowResizeEdge::Right;
}
if (top) {
return BorderlessWindowResizeEdge::Top;
}
if (bottom) {
return BorderlessWindowResizeEdge::Bottom;
}
return BorderlessWindowResizeEdge::None;
}
LPCWSTR ResolveBorderlessWindowResizeCursor(BorderlessWindowResizeEdge edge) {
switch (edge) {
case BorderlessWindowResizeEdge::Left:
case BorderlessWindowResizeEdge::Right:
return IDC_SIZEWE;
case BorderlessWindowResizeEdge::Top:
case BorderlessWindowResizeEdge::Bottom:
return IDC_SIZENS;
case BorderlessWindowResizeEdge::TopLeft:
case BorderlessWindowResizeEdge::BottomRight:
return IDC_SIZENWSE;
case BorderlessWindowResizeEdge::TopRight:
case BorderlessWindowResizeEdge::BottomLeft:
return IDC_SIZENESW;
case BorderlessWindowResizeEdge::None:
default:
return IDC_ARROW;
}
}
RECT ComputeBorderlessWindowResizeRect(
const RECT& initialRect,
const POINT& initialScreenPoint,
const POINT& currentScreenPoint,
BorderlessWindowResizeEdge edge,
int minimumOuterWidth,
int minimumOuterHeight) {
RECT result = initialRect;
const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x;
const LONG deltaY = currentScreenPoint.y - initialScreenPoint.y;
const int minimumWidth = ClampMinimum(minimumOuterWidth, 1);
const int minimumHeight = ClampMinimum(minimumOuterHeight, 1);
switch (edge) {
case BorderlessWindowResizeEdge::Left:
case BorderlessWindowResizeEdge::TopLeft:
case BorderlessWindowResizeEdge::BottomLeft:
result.left += deltaX;
if (result.right - result.left < minimumWidth) {
result.left = result.right - minimumWidth;
}
break;
case BorderlessWindowResizeEdge::None:
case BorderlessWindowResizeEdge::Top:
case BorderlessWindowResizeEdge::Bottom:
case BorderlessWindowResizeEdge::Right:
case BorderlessWindowResizeEdge::TopRight:
case BorderlessWindowResizeEdge::BottomRight:
break;
}
switch (edge) {
case BorderlessWindowResizeEdge::Right:
case BorderlessWindowResizeEdge::TopRight:
case BorderlessWindowResizeEdge::BottomRight:
result.right += deltaX;
if (result.right - result.left < minimumWidth) {
result.right = result.left + minimumWidth;
}
break;
case BorderlessWindowResizeEdge::None:
case BorderlessWindowResizeEdge::Left:
case BorderlessWindowResizeEdge::Top:
case BorderlessWindowResizeEdge::Bottom:
case BorderlessWindowResizeEdge::TopLeft:
case BorderlessWindowResizeEdge::BottomLeft:
break;
}
switch (edge) {
case BorderlessWindowResizeEdge::Top:
case BorderlessWindowResizeEdge::TopLeft:
case BorderlessWindowResizeEdge::TopRight:
result.top += deltaY;
if (result.bottom - result.top < minimumHeight) {
result.top = result.bottom - minimumHeight;
}
break;
case BorderlessWindowResizeEdge::None:
case BorderlessWindowResizeEdge::Left:
case BorderlessWindowResizeEdge::Right:
case BorderlessWindowResizeEdge::Bottom:
case BorderlessWindowResizeEdge::BottomLeft:
case BorderlessWindowResizeEdge::BottomRight:
break;
}
switch (edge) {
case BorderlessWindowResizeEdge::Bottom:
case BorderlessWindowResizeEdge::BottomLeft:
case BorderlessWindowResizeEdge::BottomRight:
result.bottom += deltaY;
if (result.bottom - result.top < minimumHeight) {
result.bottom = result.top + minimumHeight;
}
break;
case BorderlessWindowResizeEdge::None:
case BorderlessWindowResizeEdge::Left:
case BorderlessWindowResizeEdge::Top:
case BorderlessWindowResizeEdge::Right:
case BorderlessWindowResizeEdge::TopLeft:
case BorderlessWindowResizeEdge::TopRight:
break;
}
return result;
}
} // namespace XCEngine::UI::Editor::Host

View File

@@ -0,0 +1,46 @@
#pragma once
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEngine/UI/Types.h>
#include <windows.h>
namespace XCEngine::UI::Editor::Host {
enum class BorderlessWindowResizeEdge : std::uint8_t {
None = 0,
Left,
Top,
Right,
Bottom,
TopLeft,
TopRight,
BottomLeft,
BottomRight
};
struct BorderlessWindowFrameMetrics {
float resizeBorderThickness = 6.0f;
int minimumOuterWidth = 640;
int minimumOuterHeight = 360;
};
BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(
const ::XCEngine::UI::UIRect& clientRect,
const ::XCEngine::UI::UIPoint& point,
const BorderlessWindowFrameMetrics& metrics = {});
LPCWSTR ResolveBorderlessWindowResizeCursor(BorderlessWindowResizeEdge edge);
RECT ComputeBorderlessWindowResizeRect(
const RECT& initialRect,
const POINT& initialScreenPoint,
const POINT& currentScreenPoint,
BorderlessWindowResizeEdge edge,
int minimumOuterWidth,
int minimumOuterHeight);
} // namespace XCEngine::UI::Editor::Host

View File

@@ -1,14 +1,32 @@
#pragma once #pragma once
#include "BorderlessWindowFrame.h"
#include <windows.h> #include <windows.h>
namespace XCEngine::UI::Editor::Host { namespace XCEngine::UI::Editor::Host {
struct BorderlessWindowResizeState {
bool active = false;
BorderlessWindowResizeEdge edge = BorderlessWindowResizeEdge::None;
POINT initialScreenPoint = {};
RECT initialWindowRect = {};
BorderlessWindowResizeEdge hoveredEdge = BorderlessWindowResizeEdge::None;
};
struct PredictedClientPixelSize {
bool active = false;
UINT width = 0u;
UINT height = 0u;
};
class HostRuntimeState { class HostRuntimeState {
public: public:
void Reset() { void Reset() {
m_windowDpi = 96u; m_windowDpi = 96u;
m_inInteractiveResize = false; m_inInteractiveResize = false;
m_borderlessResizeState = {};
m_predictedClientPixelSize = {};
} }
void SetWindowDpi(UINT dpi) { void SetWindowDpi(UINT dpi) {
@@ -37,9 +55,83 @@ public:
return m_inInteractiveResize; return m_inInteractiveResize;
} }
void BeginBorderlessResize(
BorderlessWindowResizeEdge edge,
const POINT& initialScreenPoint,
const RECT& initialWindowRect) {
m_borderlessResizeState.active = edge != BorderlessWindowResizeEdge::None;
m_borderlessResizeState.edge = edge;
m_borderlessResizeState.initialScreenPoint = initialScreenPoint;
m_borderlessResizeState.initialWindowRect = initialWindowRect;
m_borderlessResizeState.hoveredEdge = edge;
m_inInteractiveResize = m_borderlessResizeState.active;
}
void EndBorderlessResize() {
m_borderlessResizeState.active = false;
m_borderlessResizeState.edge = BorderlessWindowResizeEdge::None;
m_inInteractiveResize = false;
m_predictedClientPixelSize = {};
}
bool IsBorderlessResizeActive() const {
return m_borderlessResizeState.active;
}
BorderlessWindowResizeEdge GetBorderlessResizeEdge() const {
return m_borderlessResizeState.edge;
}
const POINT& GetBorderlessResizeInitialScreenPoint() const {
return m_borderlessResizeState.initialScreenPoint;
}
const RECT& GetBorderlessResizeInitialWindowRect() const {
return m_borderlessResizeState.initialWindowRect;
}
void SetHoveredBorderlessResizeEdge(BorderlessWindowResizeEdge edge) {
m_borderlessResizeState.hoveredEdge = edge;
}
BorderlessWindowResizeEdge GetHoveredBorderlessResizeEdge() const {
return m_borderlessResizeState.hoveredEdge;
}
void SetPredictedClientPixelSize(UINT width, UINT height) {
if (width == 0u || height == 0u) {
m_predictedClientPixelSize = {};
return;
}
m_predictedClientPixelSize.active = true;
m_predictedClientPixelSize.width = width;
m_predictedClientPixelSize.height = height;
}
void ClearPredictedClientPixelSize() {
m_predictedClientPixelSize = {};
}
bool TryGetPredictedClientPixelSize(UINT& outWidth, UINT& outHeight) const {
outWidth = 0u;
outHeight = 0u;
if (!m_predictedClientPixelSize.active ||
m_predictedClientPixelSize.width == 0u ||
m_predictedClientPixelSize.height == 0u) {
return false;
}
outWidth = m_predictedClientPixelSize.width;
outHeight = m_predictedClientPixelSize.height;
return true;
}
private: private:
UINT m_windowDpi = 96u; UINT m_windowDpi = 96u;
bool m_inInteractiveResize = false; bool m_inInteractiveResize = false;
BorderlessWindowResizeState m_borderlessResizeState = {};
PredictedClientPixelSize m_predictedClientPixelSize = {};
}; };
} // namespace XCEngine::UI::Editor::Host } // namespace XCEngine::UI::Editor::Host