Add borderless editor host chrome
This commit is contained in:
328
new_editor/app/Host/BorderlessWindowChrome.cpp
Normal file
328
new_editor/app/Host/BorderlessWindowChrome.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user