#include "BorderlessWindowChrome.h" #include #include 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( 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( 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(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