Add borderless editor host chrome
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<float>((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<float>(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<HWND>(lParam) != hwnd &&
|
||||
application->HasInteractiveCaptureState()) {
|
||||
application->QueueWindowFocusEvent(UIInputEventType::FocusLost);
|
||||
application->ClearBorderlessWindowChromeState();
|
||||
return 0;
|
||||
}
|
||||
if (application != nullptr &&
|
||||
reinterpret_cast<HWND>(lParam) != hwnd) {
|
||||
application->ClearBorderlessWindowChromeState();
|
||||
}
|
||||
break;
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#endif
|
||||
|
||||
#include <Host/AutoScreenshot.h>
|
||||
#include <Host/BorderlessWindowChrome.h>
|
||||
#include <Host/D3D12WindowRenderer.h>
|
||||
#include <Host/D3D12WindowRenderLoop.h>
|
||||
#include <Host/HostRuntimeState.h>
|
||||
@@ -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 = {};
|
||||
};
|
||||
|
||||
|
||||
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
|
||||
84
new_editor/app/Host/BorderlessWindowChrome.h
Normal file
84
new_editor/app/Host/BorderlessWindowChrome.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
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
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user