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

@@ -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
#include "BorderlessWindowFrame.h"
#include <windows.h>
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 {
public:
void Reset() {
m_windowDpi = 96u;
m_inInteractiveResize = false;
m_borderlessResizeState = {};
m_predictedClientPixelSize = {};
}
void SetWindowDpi(UINT dpi) {
@@ -37,9 +55,83 @@ public:
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:
UINT m_windowDpi = 96u;
bool m_inInteractiveResize = false;
BorderlessWindowResizeState m_borderlessResizeState = {};
PredictedClientPixelSize m_predictedClientPixelSize = {};
};
} // namespace XCEngine::UI::Editor::Host