Files
XCEngine/new_editor/app/Host/BorderlessWindowChrome.cpp

329 lines
10 KiB
C++

#include "BorderlessWindowChrome.h"
#include <algorithm>
#include <dwmapi.h>
namespace XCEngine::UI::Editor::Host {
namespace {
using ::XCEngine::UI::UIColor;
using ::XCEngine::UI::UIDrawList;
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;
}
void AppendMinimizeGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness,
float insetX,
float insetY) {
const float y = rect.y + rect.height - insetY;
drawList.AddLine(
UIPoint(rect.x + insetX, y),
UIPoint(rect.x + rect.width - insetX, y),
color,
thickness);
}
void AppendMaximizeGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness,
float insetX,
float insetY) {
drawList.AddRectOutline(
UIRect(
rect.x + insetX,
rect.y + insetY,
(std::max)(0.0f, rect.width - insetX * 2.0f),
(std::max)(0.0f, rect.height - insetY * 2.0f)),
color,
thickness);
}
void AppendRestoreGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness,
float insetX,
float insetY) {
const float width = (std::max)(0.0f, rect.width - insetX * 2.0f);
const float height = (std::max)(0.0f, rect.height - insetY * 2.0f);
const float offset = 3.0f;
drawList.AddRectOutline(
UIRect(
rect.x + insetX + offset,
rect.y + insetY,
(std::max)(0.0f, width - offset),
(std::max)(0.0f, height - offset)),
color,
thickness);
drawList.AddRectOutline(
UIRect(
rect.x + insetX,
rect.y + insetY + offset,
(std::max)(0.0f, width - offset),
(std::max)(0.0f, height - offset)),
color,
thickness);
}
void AppendCloseGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness,
float insetX,
float insetY) {
drawList.AddLine(
UIPoint(rect.x + insetX, rect.y + insetY),
UIPoint(rect.x + rect.width - insetX, rect.y + rect.height - insetY),
color,
thickness);
drawList.AddLine(
UIPoint(rect.x + rect.width - insetX, rect.y + insetY),
UIPoint(rect.x + insetX, rect.y + rect.height - insetY),
color,
thickness);
}
::XCEngine::UI::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 {};
}
if (pressed) {
return palette.buttonPressedColor;
}
if (hovered) {
return palette.buttonHoverColor;
}
return {};
}
::XCEngine::UI::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;
}
int QuerySystemMetricForDpi(int index, UINT dpi) {
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
if (user32 != nullptr) {
using GetSystemMetricsForDpiFn = int(WINAPI*)(int, UINT);
const auto getSystemMetricsForDpi =
reinterpret_cast<GetSystemMetricsForDpiFn>(
GetProcAddress(user32, "GetSystemMetricsForDpi"));
if (getSystemMetricsForDpi != nullptr) {
return getSystemMetricsForDpi(index, dpi);
}
}
return GetSystemMetrics(index);
}
} // 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;
}
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,
metrics.iconInsetX,
metrics.iconInsetY);
break;
case BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
if (maximized) {
AppendRestoreGlyph(
drawList,
button.rect,
iconColor,
metrics.iconThickness,
metrics.iconInsetX,
metrics.iconInsetY);
} else {
AppendMaximizeGlyph(
drawList,
button.rect,
iconColor,
metrics.iconThickness,
metrics.iconInsetX,
metrics.iconInsetY);
}
break;
case BorderlessWindowChromeHitTarget::CloseButton:
AppendCloseGlyph(
drawList,
button.rect,
iconColor,
metrics.iconThickness,
metrics.iconInsetX,
metrics.iconInsetY);
break;
case BorderlessWindowChromeHitTarget::DragRegion:
case BorderlessWindowChromeHitTarget::None:
default:
break;
}
}
}
void EnableBorderlessWindowShadow(HWND hwnd) {
if (hwnd == nullptr) {
return;
}
using DwmExtendFrameIntoClientAreaFn = HRESULT(WINAPI*)(HWND, const MARGINS*);
static const auto extendFrameIntoClientArea = []() -> DwmExtendFrameIntoClientAreaFn {
HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll");
if (dwmapi == nullptr) {
dwmapi = LoadLibraryW(L"dwmapi.dll");
}
if (dwmapi == nullptr) {
return nullptr;
}
return reinterpret_cast<DwmExtendFrameIntoClientAreaFn>(
GetProcAddress(dwmapi, "DwmExtendFrameIntoClientArea"));
}();
if (extendFrameIntoClientArea != nullptr) {
const MARGINS margins = { 1, 1, 1, 1 };
extendFrameIntoClientArea(hwnd, &margins);
}
}
LRESULT HandleBorderlessWindowNcCalcSize(
HWND hwnd,
WPARAM wParam,
LPARAM lParam,
UINT dpi) {
if (wParam == FALSE || lParam == 0) {
return 0;
}
auto* params = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
if (params == nullptr) {
return 0;
}
if (IsZoomed(hwnd)) {
const int frameX =
QuerySystemMetricForDpi(SM_CXFRAME, dpi) +
QuerySystemMetricForDpi(SM_CXPADDEDBORDER, dpi);
const int frameY =
QuerySystemMetricForDpi(SM_CYFRAME, dpi) +
QuerySystemMetricForDpi(SM_CXPADDEDBORDER, dpi);
params->rgrc[0].left += frameX;
params->rgrc[0].right -= frameX;
params->rgrc[0].top += frameY;
params->rgrc[0].bottom -= frameY;
}
return 0;
}
} // namespace XCEngine::UI::Editor::Host