From 9064c2f5f279cba95f0ce2b2f44a45fa14675339 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 14 Apr 2026 01:14:45 +0800 Subject: [PATCH] Add borderless editor host chrome --- new_editor/CMakeLists.txt | 1 + new_editor/app/Application.cpp | 221 +++++++++++- new_editor/app/Application.h | 17 + .../app/Host/BorderlessWindowChrome.cpp | 328 ++++++++++++++++++ new_editor/app/Host/BorderlessWindowChrome.h | 84 +++++ .../app/Host/WindowMessageDispatcher.cpp | 12 + 6 files changed, 660 insertions(+), 3 deletions(-) create mode 100644 new_editor/app/Host/BorderlessWindowChrome.cpp create mode 100644 new_editor/app/Host/BorderlessWindowChrome.h diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 23a0e3f6..e5688636 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -124,6 +124,7 @@ target_link_libraries(XCUIEditorLib PUBLIC add_library(XCUIEditorHost STATIC app/Host/AutoScreenshot.cpp + app/Host/BorderlessWindowChrome.cpp app/Host/D3D12HostDevice.cpp app/Host/D3D12ShaderResourceDescriptorAllocator.cpp app/Host/D3D12WindowInteropContext.cpp diff --git a/new_editor/app/Application.cpp b/new_editor/app/Application.cpp index ca48087b..00c3c4bb 100644 --- a/new_editor/app/Application.cpp +++ b/new_editor/app/Application.cpp @@ -38,6 +38,8 @@ constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost"; constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor"; constexpr UINT kDefaultDpi = 96u; constexpr float kBaseDpiScale = 96.0f; +constexpr DWORD kBorderlessWindowStyle = + WS_POPUP | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; bool ResolveVerboseRuntimeTraceEnabled() { wchar_t buffer[8] = {}; @@ -406,7 +408,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { WNDCLASSEXW windowClass = {}; windowClass.cbSize = sizeof(windowClass); - windowClass.style = CS_HREDRAW | CS_VREDRAW; + windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; windowClass.lpfnWndProc = &Application::WndProc; windowClass.hInstance = hInstance; windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); @@ -418,10 +420,10 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { } m_hwnd = CreateWindowExW( - 0, + WS_EX_APPWINDOW, kWindowClassName, kWindowTitle, - WS_OVERLAPPEDWINDOW, + kBorderlessWindowStyle, CW_USEDEFAULT, CW_USEDEFAULT, 1540, @@ -434,6 +436,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { LogRuntimeTrace("app", "window creation failed"); return false; } + Host::EnableBorderlessWindowShadow(m_hwnd); m_hostRuntime.Reset(); m_hostRuntime.SetWindowDpi(QueryWindowDpi(m_hwnd)); m_renderer.SetDpiScale(GetDpiScale()); @@ -607,6 +610,8 @@ void Application::RenderFrame() { 12.0f); } + AppendBorderlessWindowChrome(drawList, width); + const Host::D3D12WindowRenderLoopPresentResult presentResult = m_windowRenderLoop.Present(drawData); if (!presentResult.warning.empty()) { @@ -631,6 +636,18 @@ void Application::OnPaintMessage() { EndPaint(m_hwnd, &paintStruct); } +bool Application::IsBorderlessWindowEnabled() const { + return true; +} + +LRESULT Application::HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const { + return Host::HandleBorderlessWindowNcCalcSize( + m_hwnd, + wParam, + lParam, + m_hostRuntime.GetWindowDpi()); +} + float Application::GetDpiScale() const { return m_hostRuntime.GetDpiScale(kBaseDpiScale); } @@ -690,6 +707,175 @@ bool Application::ApplyCurrentCursor() const { return true; } +Host::BorderlessWindowChromeLayout Application::ResolveBorderlessWindowChromeLayout( + float clientWidthDips) const { + const auto& menuBarMetrics = ResolveUIEditorMenuBarMetrics(); + ::XCEngine::UI::UIRect titleBarRect(0.0f, 0.0f, clientWidthDips, menuBarMetrics.barHeight); + float leadingOccupiedRight = titleBarRect.x; + + const UIEditorShellInteractionFrame& shellFrame = m_editorWorkspace.GetShellFrame(); + if (shellFrame.shellFrame.layout.menuBarRect.width > 0.0f && + shellFrame.shellFrame.layout.menuBarRect.height > 0.0f) { + titleBarRect = shellFrame.shellFrame.layout.menuBarRect; + } + + const auto& buttonRects = shellFrame.shellFrame.layout.menuBarLayout.buttonRects; + if (!buttonRects.empty()) { + const auto& lastRect = buttonRects.back(); + leadingOccupiedRight = lastRect.x + lastRect.width; + } + + return Host::BuildBorderlessWindowChromeLayout(titleBarRect, leadingOccupiedRight); +} + +Host::BorderlessWindowChromeHitTarget Application::HitTestBorderlessWindowChrome(LPARAM lParam) const { + if (!IsBorderlessWindowEnabled() || m_hwnd == nullptr) { + return Host::BorderlessWindowChromeHitTarget::None; + } + + RECT clientRect = {}; + if (!GetClientRect(m_hwnd, &clientRect)) { + return Host::BorderlessWindowChromeHitTarget::None; + } + + const float clientWidthDips = PixelsToDips( + static_cast((std::max)(clientRect.right - clientRect.left, 1L))); + const Host::BorderlessWindowChromeLayout layout = + ResolveBorderlessWindowChromeLayout(clientWidthDips); + return Host::HitTestBorderlessWindowChrome( + layout, + ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); +} + +bool Application::UpdateBorderlessWindowChromeHover(LPARAM lParam) { + const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam); + const Host::BorderlessWindowChromeHitTarget buttonTarget = + hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || + hitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || + hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton + ? hitTarget + : Host::BorderlessWindowChromeHitTarget::None; + if (m_borderlessWindowChromeState.hoveredTarget == buttonTarget) { + return false; + } + + m_borderlessWindowChromeState.hoveredTarget = buttonTarget; + return true; +} + +bool Application::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) { + const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam); + switch (hitTarget) { + case Host::BorderlessWindowChromeHitTarget::MinimizeButton: + case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: + case Host::BorderlessWindowChromeHitTarget::CloseButton: + m_borderlessWindowChromeState.pressedTarget = hitTarget; + if (m_hwnd != nullptr) { + SetCapture(m_hwnd); + } + InvalidateHostWindow(); + return true; + case Host::BorderlessWindowChromeHitTarget::DragRegion: + if (m_hwnd != nullptr) { + ReleaseCapture(); + SendMessageW(m_hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + return true; + case Host::BorderlessWindowChromeHitTarget::None: + default: + return false; + } +} + +bool Application::HandleBorderlessWindowChromeButtonUp(LPARAM lParam) { + const Host::BorderlessWindowChromeHitTarget pressedTarget = + m_borderlessWindowChromeState.pressedTarget; + if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton && + pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton && + pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) { + return false; + } + + const Host::BorderlessWindowChromeHitTarget releasedTarget = + HitTestBorderlessWindowChrome(lParam); + m_borderlessWindowChromeState.pressedTarget = Host::BorderlessWindowChromeHitTarget::None; + if (GetCapture() == m_hwnd) { + ReleaseCapture(); + } + InvalidateHostWindow(); + + if (pressedTarget == releasedTarget) { + ExecuteBorderlessWindowChromeAction(pressedTarget); + } + return true; +} + +bool Application::HandleBorderlessWindowChromeDoubleClick(LPARAM lParam) { + if (HitTestBorderlessWindowChrome(lParam) != Host::BorderlessWindowChromeHitTarget::DragRegion) { + return false; + } + + ExecuteBorderlessWindowChromeAction(Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton); + return true; +} + +void Application::ClearBorderlessWindowChromeState() { + if (m_borderlessWindowChromeState.hoveredTarget == Host::BorderlessWindowChromeHitTarget::None && + m_borderlessWindowChromeState.pressedTarget == Host::BorderlessWindowChromeHitTarget::None) { + return; + } + + m_borderlessWindowChromeState = {}; + InvalidateHostWindow(); +} + +void Application::AppendBorderlessWindowChrome( + ::XCEngine::UI::UIDrawList& drawList, + float clientWidthDips) const { + if (!IsBorderlessWindowEnabled()) { + return; + } + + const Host::BorderlessWindowChromeLayout layout = + ResolveBorderlessWindowChromeLayout(clientWidthDips); + Host::AppendBorderlessWindowChrome( + drawList, + layout, + m_borderlessWindowChromeState, + m_hwnd != nullptr && IsZoomed(m_hwnd)); +} + +void Application::ExecuteBorderlessWindowChromeAction( + Host::BorderlessWindowChromeHitTarget target) { + if (m_hwnd == nullptr) { + return; + } + + switch (target) { + case Host::BorderlessWindowChromeHitTarget::MinimizeButton: + ShowWindow(m_hwnd, SW_MINIMIZE); + break; + case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: + ShowWindow(m_hwnd, IsZoomed(m_hwnd) ? SW_RESTORE : SW_MAXIMIZE); + break; + case Host::BorderlessWindowChromeHitTarget::CloseButton: + PostMessageW(m_hwnd, WM_CLOSE, 0, 0); + break; + case Host::BorderlessWindowChromeHitTarget::DragRegion: + case Host::BorderlessWindowChromeHitTarget::None: + default: + break; + } + + InvalidateHostWindow(); +} + +void Application::InvalidateHostWindow() const { + if (m_hwnd != nullptr && IsWindow(m_hwnd)) { + InvalidateRect(m_hwnd, nullptr, FALSE); + } +} + UIPoint Application::ConvertClientPixelsToDips(LONG x, LONG y) const { return UIPoint( PixelsToDips(static_cast(x)), @@ -822,6 +1008,7 @@ void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) { ApplyWindowResize(clientWidth, clientHeight); } + Host::EnableBorderlessWindowShadow(m_hwnd); } std::ostringstream trace = {}; @@ -969,6 +1156,9 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP switch (message) { case WM_MOUSEMOVE: if (application != nullptr) { + if (application->UpdateBorderlessWindowChromeHover(lParam)) { + application->InvalidateHostWindow(); + } if (!application->m_trackingMouseLeave) { TRACKMOUSEEVENT trackMouseEvent = {}; trackMouseEvent.cbSize = sizeof(trackMouseEvent); @@ -978,6 +1168,13 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP application->m_trackingMouseLeave = true; } } + const Host::BorderlessWindowChromeHitTarget chromeHitTarget = + application->HitTestBorderlessWindowChrome(lParam); + if (chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || + chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || + chromeHitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton) { + return 0; + } application->QueuePointerEvent( UIInputEventType::PointerMove, UIPointerButton::None, @@ -989,12 +1186,16 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP case WM_MOUSELEAVE: if (application != nullptr) { application->m_trackingMouseLeave = false; + application->ClearBorderlessWindowChromeState(); application->QueuePointerLeaveEvent(); return 0; } break; case WM_LBUTTONDOWN: if (application != nullptr) { + if (application->HandleBorderlessWindowChromeButtonDown(lParam)) { + return 0; + } SetFocus(hwnd); application->QueuePointerEvent( UIInputEventType::PointerButtonDown, @@ -1006,6 +1207,9 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP break; case WM_LBUTTONUP: if (application != nullptr) { + if (application->HandleBorderlessWindowChromeButtonUp(lParam)) { + return 0; + } application->QueuePointerEvent( UIInputEventType::PointerButtonUp, UIPointerButton::Left, @@ -1014,6 +1218,12 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP return 0; } break; + case WM_LBUTTONDBLCLK: + if (application != nullptr && + application->HandleBorderlessWindowChromeDoubleClick(lParam)) { + return 0; + } + break; case WM_MOUSEWHEEL: if (application != nullptr) { application->QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam); @@ -1039,8 +1249,13 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP reinterpret_cast(lParam) != hwnd && application->HasInteractiveCaptureState()) { application->QueueWindowFocusEvent(UIInputEventType::FocusLost); + application->ClearBorderlessWindowChromeState(); return 0; } + if (application != nullptr && + reinterpret_cast(lParam) != hwnd) { + application->ClearBorderlessWindowChromeState(); + } break; case WM_KEYDOWN: case WM_SYSKEYDOWN: diff --git a/new_editor/app/Application.h b/new_editor/app/Application.h index ef9dce75..3dfd6070 100644 --- a/new_editor/app/Application.h +++ b/new_editor/app/Application.h @@ -5,6 +5,7 @@ #endif #include +#include #include #include #include @@ -75,6 +76,21 @@ private: void QueueKeyEvent(::XCEngine::UI::UIInputEventType type, WPARAM wParam, LPARAM lParam); void QueueCharacterEvent(WPARAM wParam, LPARAM lParam); void QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType type); + bool IsBorderlessWindowEnabled() const; + LRESULT HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const; + Host::BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(LPARAM lParam) const; + bool UpdateBorderlessWindowChromeHover(LPARAM lParam); + bool HandleBorderlessWindowChromeButtonDown(LPARAM lParam); + bool HandleBorderlessWindowChromeButtonUp(LPARAM lParam); + bool HandleBorderlessWindowChromeDoubleClick(LPARAM lParam); + void ClearBorderlessWindowChromeState(); + void AppendBorderlessWindowChrome( + ::XCEngine::UI::UIDrawList& drawList, + float clientWidthDips) const; + void ExecuteBorderlessWindowChromeAction(Host::BorderlessWindowChromeHitTarget target); + Host::BorderlessWindowChromeLayout ResolveBorderlessWindowChromeLayout( + float clientWidthDips) const; + void InvalidateHostWindow() const; static std::filesystem::path ResolveRepoRootPath(); static LONG WINAPI HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo); static bool IsVerboseRuntimeTraceEnabled(); @@ -92,6 +108,7 @@ private: std::vector<::XCEngine::UI::UIInputEvent> m_pendingInputEvents = {}; bool m_trackingMouseLeave = false; bool m_renderReady = false; + ::XCEngine::UI::Editor::Host::BorderlessWindowChromeState m_borderlessWindowChromeState = {}; ::XCEngine::UI::Editor::Host::HostRuntimeState m_hostRuntime = {}; }; diff --git a/new_editor/app/Host/BorderlessWindowChrome.cpp b/new_editor/app/Host/BorderlessWindowChrome.cpp new file mode 100644 index 00000000..90955b79 --- /dev/null +++ b/new_editor/app/Host/BorderlessWindowChrome.cpp @@ -0,0 +1,328 @@ +#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 diff --git a/new_editor/app/Host/BorderlessWindowChrome.h b/new_editor/app/Host/BorderlessWindowChrome.h new file mode 100644 index 00000000..eff5c2cf --- /dev/null +++ b/new_editor/app/Host/BorderlessWindowChrome.h @@ -0,0 +1,84 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +#include + +namespace XCEngine::UI::Editor::Host { + +enum class BorderlessWindowChromeHitTarget : std::uint8_t { + None = 0, + DragRegion, + MinimizeButton, + MaximizeRestoreButton, + CloseButton +}; + +struct BorderlessWindowChromeMetrics { + float buttonWidth = 45.0f; + float buttonInsetX = 0.0f; + float dragPaddingLeft = 8.0f; + float dragPaddingRight = 8.0f; + float iconInsetX = 16.0f; + float iconInsetY = 8.0f; + float iconThickness = 1.2f; +}; + +struct BorderlessWindowChromePalette { + ::XCEngine::UI::UIColor buttonHoverColor = + ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor buttonPressedColor = + ::XCEngine::UI::UIColor(0.78f, 0.78f, 0.78f, 1.0f); + ::XCEngine::UI::UIColor closeButtonHoverColor = + ::XCEngine::UI::UIColor(0.91f, 0.31f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor closeButtonPressedColor = + ::XCEngine::UI::UIColor(0.78f, 0.22f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor iconColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor closeIconHoverColor = + ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f); +}; + +struct BorderlessWindowChromeState { + BorderlessWindowChromeHitTarget hoveredTarget = BorderlessWindowChromeHitTarget::None; + BorderlessWindowChromeHitTarget pressedTarget = BorderlessWindowChromeHitTarget::None; +}; + +struct BorderlessWindowChromeLayout { + ::XCEngine::UI::UIRect titleBarRect = {}; + ::XCEngine::UI::UIRect dragRect = {}; + ::XCEngine::UI::UIRect minimizeButtonRect = {}; + ::XCEngine::UI::UIRect maximizeRestoreButtonRect = {}; + ::XCEngine::UI::UIRect closeButtonRect = {}; +}; + +BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout( + const ::XCEngine::UI::UIRect& titleBarRect, + float leadingOccupiedRight, + const BorderlessWindowChromeMetrics& metrics = {}); + +BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome( + const BorderlessWindowChromeLayout& layout, + const ::XCEngine::UI::UIPoint& point); + +void AppendBorderlessWindowChrome( + ::XCEngine::UI::UIDrawList& drawList, + const BorderlessWindowChromeLayout& layout, + const BorderlessWindowChromeState& state, + bool maximized, + const BorderlessWindowChromePalette& palette = {}, + const BorderlessWindowChromeMetrics& metrics = {}); + +void EnableBorderlessWindowShadow(HWND hwnd); + +LRESULT HandleBorderlessWindowNcCalcSize( + HWND hwnd, + WPARAM wParam, + LPARAM lParam, + UINT dpi); + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/WindowMessageDispatcher.cpp b/new_editor/app/Host/WindowMessageDispatcher.cpp index 56f19a6d..db906353 100644 --- a/new_editor/app/Host/WindowMessageDispatcher.cpp +++ b/new_editor/app/Host/WindowMessageDispatcher.cpp @@ -63,6 +63,18 @@ bool WindowMessageDispatcher::TryDispatch( }; switch (message) { + case WM_NCCALCSIZE: + if (application.IsBorderlessWindowEnabled()) { + outResult = application.HandleBorderlessWindowNcCalcSize(wParam, lParam); + return true; + } + return false; + case WM_NCACTIVATE: + if (application.IsBorderlessWindowEnabled()) { + outResult = TRUE; + return true; + } + return false; case WM_SETCURSOR: if (LOWORD(lParam) == HTCLIENT && application.ApplyCurrentCursor()) { outResult = TRUE;