Files
XCEngine/new_editor/app/Platform/Win32/BorderlessWindowChrome.cpp

426 lines
14 KiB
C++
Raw Normal View History

#include "BorderlessWindowChrome.h"
#include <algorithm>
#include <cmath>
#include <dwmapi.h>
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;
}
} // namespace
BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout(
const UIRect& titleBarRect,
float leadingOccupiedRight,
const BorderlessWindowChromeMetrics& metrics) {
BorderlessWindowChromeLayout layout = {};
layout.titleBarRect = titleBarRect;
if (titleBarRect.width <= 0.0f || titleBarRect.height <= 0.0f) {
return layout;
}
const float buttonWidth = (std::max)(metrics.buttonWidth, 0.0f);
const float buttonX3 = titleBarRect.x + titleBarRect.width - metrics.buttonInsetX - buttonWidth;
const float buttonX2 = buttonX3 - buttonWidth;
const float buttonX1 = buttonX2 - buttonWidth;
layout.minimizeButtonRect = UIRect(buttonX1, titleBarRect.y, buttonWidth, titleBarRect.height);
layout.maximizeRestoreButtonRect = UIRect(buttonX2, titleBarRect.y, buttonWidth, titleBarRect.height);
layout.closeButtonRect = UIRect(buttonX3, titleBarRect.y, buttonWidth, titleBarRect.height);
const float dragLeft =
(std::max)(titleBarRect.x, leadingOccupiedRight + metrics.dragPaddingLeft);
const float dragRight =
(std::max)(dragLeft, layout.minimizeButtonRect.x - metrics.dragPaddingRight);
layout.dragRect = UIRect(
dragLeft,
titleBarRect.y,
(std::max)(0.0f, dragRight - dragLeft),
titleBarRect.height);
return layout;
}
BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(
const BorderlessWindowChromeLayout& layout,
const UIPoint& point) {
if (IsPointInsideRect(layout.closeButtonRect, point)) {
return BorderlessWindowChromeHitTarget::CloseButton;
}
if (IsPointInsideRect(layout.maximizeRestoreButtonRect, point)) {
return BorderlessWindowChromeHitTarget::MaximizeRestoreButton;
}
if (IsPointInsideRect(layout.minimizeButtonRect, point)) {
return BorderlessWindowChromeHitTarget::MinimizeButton;
}
if (IsPointInsideRect(layout.dragRect, point)) {
return BorderlessWindowChromeHitTarget::DragRegion;
}
return BorderlessWindowChromeHitTarget::None;
}
} // namespace XCEngine::UI::Editor::Host
namespace XCEngine::UI::Editor::Host {
namespace {
using ::XCEngine::UI::UIColor;
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
constexpr UIColor kTransparentColor(0.0f, 0.0f, 0.0f, 0.0f);
float ResolveGlyphBoxSize(const UIRect& rect, float ratio, float minSize) {
return (std::max)(minSize, static_cast<float>(std::round(rect.height * ratio)));
}
void AppendMinimizeGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness) {
const float centerX = rect.x + rect.width * 0.5f;
const float centerY = rect.y + rect.height * 0.5f;
const float halfWidth = ResolveGlyphBoxSize(rect, 0.38f, 10.0f) * 0.5f;
const float y = centerY;
drawList.AddLine(
UIPoint(centerX - halfWidth, y),
UIPoint(centerX + halfWidth, y),
color,
thickness);
}
void AppendMaximizeGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness) {
const float centerX = rect.x + rect.width * 0.5f;
const float centerY = rect.y + rect.height * 0.5f;
const float boxSize = ResolveGlyphBoxSize(rect, 0.32f, 9.0f);
const float halfExtent = boxSize * 0.5f;
const float left = centerX - halfExtent;
const float top = centerY - halfExtent;
const float right = left + boxSize;
const float bottom = top + boxSize;
drawList.AddLine(UIPoint(left, top), UIPoint(right, top), color, thickness);
drawList.AddLine(UIPoint(left, top), UIPoint(left, bottom), color, thickness);
drawList.AddLine(UIPoint(right, top), UIPoint(right, bottom), color, thickness);
drawList.AddLine(UIPoint(left, bottom), UIPoint(right, bottom), color, thickness);
}
void AppendRestoreGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness) {
const float centerX = rect.x + rect.width * 0.5f;
const float centerY = rect.y + rect.height * 0.5f;
const float boxSize = ResolveGlyphBoxSize(rect, 0.29f, 8.0f);
const float halfExtent = boxSize * 0.5f;
const float offset = 1.0f;
const float backLeft = centerX - halfExtent + offset;
const float backTop = centerY - halfExtent - offset;
const float backRight = backLeft + boxSize;
const float backBottom = backTop + boxSize;
const float frontLeft = centerX - halfExtent - offset;
const float frontTop = centerY - halfExtent + offset;
const float frontRight = frontLeft + boxSize;
const float frontBottom = frontTop + boxSize;
drawList.AddLine(UIPoint(backLeft, backTop), UIPoint(backRight, backTop), color, thickness);
drawList.AddLine(UIPoint(backLeft, backTop), UIPoint(backLeft, frontTop), color, thickness);
drawList.AddLine(UIPoint(backRight, backTop), UIPoint(backRight, backBottom), color, thickness);
drawList.AddLine(UIPoint(frontRight, backBottom), UIPoint(backRight, backBottom), color, thickness);
drawList.AddLine(UIPoint(frontLeft, frontTop), UIPoint(frontRight, frontTop), color, thickness);
drawList.AddLine(UIPoint(frontLeft, frontTop), UIPoint(frontLeft, frontBottom), color, thickness);
drawList.AddLine(UIPoint(frontRight, frontTop), UIPoint(frontRight, frontBottom), color, thickness);
drawList.AddLine(UIPoint(frontLeft, frontBottom), UIPoint(frontRight, frontBottom), color, thickness);
}
void AppendCloseGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness) {
const float centerX = rect.x + rect.width * 0.5f;
const float centerY = rect.y + rect.height * 0.5f;
const float halfWidth = ResolveGlyphBoxSize(rect, 0.29f, 8.0f) * 0.5f;
const float halfHeight = halfWidth;
drawList.AddLine(
UIPoint(centerX - halfWidth, centerY - halfHeight),
UIPoint(centerX + halfWidth, centerY + halfHeight),
color,
thickness);
drawList.AddLine(
UIPoint(centerX + halfWidth, centerY - halfHeight),
UIPoint(centerX - halfWidth, centerY + halfHeight),
color,
thickness);
}
UIColor ResolveButtonFillColor(
BorderlessWindowChromeHitTarget target,
const BorderlessWindowChromeState& state,
const BorderlessWindowChromePalette& palette) {
const bool hovered = state.hoveredTarget == target;
const bool pressed = state.pressedTarget == target;
if (target == BorderlessWindowChromeHitTarget::CloseButton) {
if (pressed) {
return palette.closeButtonPressedColor;
}
if (hovered) {
return palette.closeButtonHoverColor;
}
return kTransparentColor;
}
if (pressed) {
return palette.buttonPressedColor;
}
if (hovered) {
return palette.buttonHoverColor;
}
return kTransparentColor;
}
UIColor ResolveIconColor(
BorderlessWindowChromeHitTarget target,
const BorderlessWindowChromeState& state,
const BorderlessWindowChromePalette& palette) {
if (target == BorderlessWindowChromeHitTarget::CloseButton &&
(state.hoveredTarget == target || state.pressedTarget == target)) {
return palette.closeIconHoverColor;
}
return palette.iconColor;
}
} // namespace
void AppendBorderlessWindowChrome(
UIDrawList& drawList,
const BorderlessWindowChromeLayout& layout,
const BorderlessWindowChromeState& state,
bool maximized,
const BorderlessWindowChromePalette& palette,
const BorderlessWindowChromeMetrics& metrics) {
const struct ButtonEntry {
BorderlessWindowChromeHitTarget target;
UIRect rect;
} buttons[] = {
{ BorderlessWindowChromeHitTarget::MinimizeButton, layout.minimizeButtonRect },
{ BorderlessWindowChromeHitTarget::MaximizeRestoreButton, layout.maximizeRestoreButtonRect },
{ BorderlessWindowChromeHitTarget::CloseButton, layout.closeButtonRect }
};
for (const ButtonEntry& button : buttons) {
const UIColor fill = ResolveButtonFillColor(button.target, state, palette);
if (fill.a > 0.0f) {
drawList.AddFilledRect(button.rect, fill);
}
const UIColor iconColor = ResolveIconColor(button.target, state, palette);
switch (button.target) {
case BorderlessWindowChromeHitTarget::MinimizeButton:
AppendMinimizeGlyph(drawList, button.rect, iconColor, metrics.iconThickness);
break;
case BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
if (maximized) {
AppendRestoreGlyph(drawList, button.rect, iconColor, metrics.iconThickness);
} else {
AppendMaximizeGlyph(drawList, button.rect, iconColor, metrics.iconThickness);
}
break;
case BorderlessWindowChromeHitTarget::CloseButton:
AppendCloseGlyph(drawList, button.rect, iconColor, metrics.iconThickness);
break;
case BorderlessWindowChromeHitTarget::DragRegion:
case BorderlessWindowChromeHitTarget::None:
default:
break;
}
}
}
} // namespace XCEngine::UI::Editor::Host
namespace XCEngine::UI::Editor::Host {
namespace {
using DwmSetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD);
using DwmExtendFrameIntoClientAreaFn = HRESULT(WINAPI*)(HWND, const MARGINS*);
HMODULE GetDwmApiModule() {
static HMODULE dwmapi = []() -> HMODULE {
HMODULE module = GetModuleHandleW(L"dwmapi.dll");
if (module == nullptr) {
module = LoadLibraryW(L"dwmapi.dll");
}
return module;
}();
return dwmapi;
}
template <typename ProcedureType>
ProcedureType GetDwmApiProcedure(const char* name) {
const HMODULE dwmapi = GetDwmApiModule();
if (dwmapi == nullptr) {
return nullptr;
}
return reinterpret_cast<ProcedureType>(GetProcAddress(dwmapi, name));
}
bool IsWindowAlignedToMonitorWorkArea(HWND hwnd) {
if (hwnd == nullptr) {
return false;
}
RECT windowRect = {};
if (!GetWindowRect(hwnd, &windowRect)) {
return false;
}
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor == nullptr) {
return false;
}
MONITORINFO monitorInfo = {};
monitorInfo.cbSize = sizeof(monitorInfo);
if (!GetMonitorInfoW(monitor, &monitorInfo)) {
return false;
}
const RECT& workArea = monitorInfo.rcWork;
return windowRect.left == workArea.left &&
windowRect.top == workArea.top &&
windowRect.right == workArea.right &&
windowRect.bottom == workArea.bottom;
}
template <typename ValueType>
void ApplyDwmWindowAttribute(HWND hwnd, DWORD attribute, const ValueType& value) {
if (hwnd == nullptr) {
return;
}
static const DwmSetWindowAttributeFn setWindowAttribute =
GetDwmApiProcedure<DwmSetWindowAttributeFn>("DwmSetWindowAttribute");
if (setWindowAttribute != nullptr) {
setWindowAttribute(hwnd, attribute, &value, sizeof(value));
}
}
} // namespace
void EnableBorderlessWindowShadow(HWND hwnd) {
if (hwnd == nullptr) {
return;
}
static const DwmExtendFrameIntoClientAreaFn extendFrameIntoClientArea =
GetDwmApiProcedure<DwmExtendFrameIntoClientAreaFn>("DwmExtendFrameIntoClientArea");
if (extendFrameIntoClientArea != nullptr) {
const bool maximized = IsZoomed(hwnd) || IsWindowAlignedToMonitorWorkArea(hwnd);
const MARGINS margins = maximized
? MARGINS{ 0, 0, 0, 0 }
: MARGINS{ 1, 1, 1, 1 };
extendFrameIntoClientArea(hwnd, &margins);
}
}
void RefreshBorderlessWindowDwmDecorations(HWND hwnd) {
if (hwnd == nullptr) {
return;
}
const int ncRenderingPolicy = DWMNCRP_DISABLED;
const BOOL allowNcPaint = FALSE;
const BOOL disableTransitions = TRUE;
const BOOL useImmersiveDarkMode = TRUE;
const COLORREF captionColor = RGB(26, 26, 26);
const COLORREF textColor = RGB(235, 235, 235);
const COLORREF borderColor = RGB(26, 26, 26);
ApplyDwmWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, ncRenderingPolicy);
ApplyDwmWindowAttribute(hwnd, DWMWA_ALLOW_NCPAINT, allowNcPaint);
ApplyDwmWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, disableTransitions);
ApplyDwmWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, useImmersiveDarkMode);
ApplyDwmWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, captionColor);
ApplyDwmWindowAttribute(hwnd, DWMWA_TEXT_COLOR, textColor);
ApplyDwmWindowAttribute(hwnd, DWMWA_BORDER_COLOR, borderColor);
EnableBorderlessWindowShadow(hwnd);
}
bool HandleBorderlessWindowGetMinMaxInfo(
HWND hwnd,
LPARAM lParam,
int minimumOuterWidth,
int minimumOuterHeight) {
if (hwnd == nullptr || lParam == 0) {
return false;
}
auto* minMaxInfo = reinterpret_cast<MINMAXINFO*>(lParam);
if (minMaxInfo == nullptr) {
return false;
}
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor == nullptr) {
return false;
}
MONITORINFO monitorInfo = {};
monitorInfo.cbSize = sizeof(monitorInfo);
if (!GetMonitorInfoW(monitor, &monitorInfo)) {
return false;
}
const RECT& workArea = monitorInfo.rcWork;
const RECT& monitorArea = monitorInfo.rcMonitor;
minMaxInfo->ptMaxPosition.x = workArea.left - monitorArea.left;
minMaxInfo->ptMaxPosition.y = workArea.top - monitorArea.top;
minMaxInfo->ptMaxSize.x = workArea.right - workArea.left;
minMaxInfo->ptMaxSize.y = workArea.bottom - workArea.top;
minMaxInfo->ptMaxTrackSize = minMaxInfo->ptMaxSize;
minMaxInfo->ptMinTrackSize.x = (std::max)(minimumOuterWidth, 1);
minMaxInfo->ptMinTrackSize.y = (std::max)(minimumOuterHeight, 1);
return true;
}
LRESULT HandleBorderlessWindowNcCalcSize(
HWND hwnd,
WPARAM wParam,
LPARAM lParam,
UINT dpi) {
(void)hwnd;
(void)wParam;
(void)lParam;
(void)dpi;
return 0;
}
} // namespace XCEngine::UI::Editor::Host