DYTSrouce/src/ui/FramelessDelegateWin.cpp

491 lines
15 KiB
C++
Raw Normal View History

2025-01-04 04:12:51 +00:00
#include "FramelessDelegateWin.h"
#include <WinUser.h>
#include <windowsx.h>
#include <dwmapi.h>
#include <objidl.h> // Fixes error C2504: 'IUnknown' : base class undefined
#include <algorithm>
namespace Gdiplus
{
using std::min;
using std::max;
};
#include <gdiplus.h>
#include <GdiPlusColor.h>
#include <QFocusEvent>
#include <QApplication>
#include <QWindow>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QtWin>
#endif
#include "FrameTitleBar.h"
#pragma comment (lib,"Dwmapi.lib") // Adds missing library, fixes error LNK2019: unresolved external symbol __imp__DwmExtendFrameIntoClientArea
#pragma comment (lib,"user32.lib")
constexpr unsigned long BorderlessFlag = WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
inline bool IsCompositionEnabled() {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return QtWin::isCompositionEnabled();
#else
BOOL enabled = FALSE;
DwmIsCompositionEnabled(&enabled);
return enabled;
#endif
}
inline void ExtendFrameIntoClientArea(QWindow* window, int left, int top, int right, int bottom) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QtWin::extendFrameIntoClientArea(window, left, top, right, bottom);
#else
MARGINS margins = { left, right, top, bottom };
DwmExtendFrameIntoClientArea(reinterpret_cast<HWND>(window->winId()), &margins);
#endif
}
inline std::optional<QRect> GetScreenNativeWorkRect(HWND hwnd) {
auto monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
if (!monitor)
return std::nullopt;
MONITORINFO info;
::ZeroMemory(&info, sizeof(info));
info.cbSize = sizeof(info);
GetMonitorInfo(monitor, &info);
return QRect(info.rcWork.left, info.rcWork.top, info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top);
}
FramelessDelegate* FramelessDelegate::Create(QWidget* paranet) {
return new FramelessDelegateWin(paranet);
}
FramelessDelegateWin::FramelessDelegateWin(QWidget* parent)
: FramelessDelegate(parent){
}
FramelessDelegateWin::~FramelessDelegateWin() = default;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
bool FramelessDelegateWin::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) {
#else
bool FramelessDelegateWin::nativeEvent(const QByteArray & eventType, void* message, long* result) {
#endif
if (nullptr == mainWidget_) {
return false;
}
#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
MSG* msg = *reinterpret_cast<MSG**>(message);
#else
MSG* msg = reinterpret_cast<MSG*>(message);
#endif
switch (msg->message) {
case WM_NCCALCSIZE: {
if (msg->wParam) {
if (IsZoomed(msg->hwnd)) {
CheckMonitorChanged();
auto rc = workRect_;
if (!rc)
return false;
if (auto ret = DefWindowProcW(msg->hwnd, WM_NCCALCSIZE, msg->wParam, msg->lParam); ret) {
*result = ret;
return true;
}
NCCALCSIZE_PARAMS* ncParam = reinterpret_cast<NCCALCSIZE_PARAMS*>(msg->lParam);
ncParam->rgrc[0].top = rc->top();
ncParam->rgrc[0].bottom = rc->bottom() + 1;
*result = 0;
return true;
} else {
const auto clientRect = reinterpret_cast<LPRECT>(msg->lParam);
const auto before = *clientRect;
if (auto ret = DefWindowProcW(msg->hwnd, WM_NCCALCSIZE, msg->wParam, msg->lParam); ret) {
*result = ret;
return true;
}
if (!IsCompositionEnabled()) {
*clientRect = before;
} else {
clientRect->top = before.top;
clientRect->left = before.left;
clientRect->right = before.right;
clientRect->bottom = before.bottom + 1;
}
}
}
*result = !msg->wParam ? 0 : WVR_REDRAW;
return true;
}
case WM_NCPAINT: {
if (!IsCompositionEnabled()) {
*result = 0;
return true;
} else {
break;
}
}
case WM_SETFOCUS: {
Qt::FocusReason reason;
if (::GetKeyState(VK_LBUTTON) < 0 || ::GetKeyState(VK_RBUTTON) < 0)
reason = Qt::MouseFocusReason;
else if (::GetKeyState(VK_SHIFT) < 0)
reason = Qt::BacktabFocusReason;
else
reason = Qt::TabFocusReason;
QFocusEvent e(QEvent::FocusIn, reason);
QApplication::sendEvent(mainWidget_, &e);
}
break;
case WM_NCACTIVATE: {
if (!IsCompositionEnabled()) {
*result = 1;
return true;
}
}
break;
case WM_NCHITTEST: {
return OnNCTitTest(msg, result);
}
case WM_GETMINMAXINFO:
{
if (::IsZoomed(msg->hwnd)) {
RECT frame = { 0, 0, 0, 0 };
AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);
double dpr = mainWidget_->devicePixelRatioF();
frames_.setLeft(abs(frame.left) / dpr + 0.5);
frames_.setTop(abs(frame.bottom) / dpr + 0.5);
frames_.setRight(abs(frame.right) / dpr + 0.5);
frames_.setBottom(abs(frame.bottom) / dpr + 0.5);
mainWidget_->setContentsMargins(frames_.left() + margins_.left(), \
frames_.top() + margins_.top(), \
frames_.right() + margins_.right(), \
frames_.bottom() + margins_.bottom());
justMaximized_ = true;
} else {
if (justMaximized_) {
mainWidget_->setContentsMargins(margins_);
frames_ = QMargins();
justMaximized_ = false;
}
}
unsigned int flag = FrameTitleBar::FTB_MAX & titleBar_->GetSysteButton();
if (nullptr != titleBar_ && flag) {
titleBar_->OnMaximized(justMaximized_);
}
return false;
}
case WM_NCLBUTTONDBLCLK: {
if (mainWidget_->maximumSize() == QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX) &&
mainWidget_->windowFlags().testFlag(Qt::WindowMaximizeButtonHint))
return false;
else
return true;
}
case WM_NCRBUTTONUP:
{
ShowSystemMenu(QCursor::pos());
*result = 0;
return true;
}
default:
break;
}
return false;
}
void FramelessDelegateWin::OnShow() {
if (isFirstShow_) {
isFirstShow_ = false;
SetNativeWindowLong();
}
mainWidget_->setAttribute(Qt::WA_Resized);
if (!isFirstShow_ && mainWidget_->isMaximized()) {
QMetaObject::invokeMethod(
this,
[=]() {
if (wndPlaceMent_.showCmd != SW_SHOWMINIMIZED)
SetWindowPlacement((HWND)mainWidget_->winId(), &wndPlaceMent_);
},
Qt::QueuedConnection
);
}
}
void FramelessDelegateWin::OnHide() {
wndPlaceMent_.length = sizeof(wndPlaceMent_);
::GetWindowPlacement(reinterpret_cast<HWND>(mainWidget_->winId()), &wndPlaceMent_);
}
void FramelessDelegateWin::OnClose() {
if (mainWidget_->windowFlags().testFlag(Qt::Dialog)) {
wndPlaceMent_.length = sizeof(wndPlaceMent_);
::GetWindowPlacement(reinterpret_cast<HWND>(mainWidget_->winId()), &wndPlaceMent_);
}
}
void FramelessDelegateWin::OnScreenChangeInternaal() {
if (mainWidget_->internalWinId()) {
SetWindowPos(reinterpret_cast<HWND>(mainWidget_->winId()), nullptr, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
}
}
void FramelessDelegateWin::AddMoveBar(QWidget* moveWidget) {
if (!IsCompositionEnabled() && qobject_cast<FrameTitleBar*>(moveWidget)) {
moveWidget->winId();
}
if (!moveBars_.contains(moveWidget))
moveBars_.append(moveWidget);
}
double FramelessDelegateWin::DpiScale(double value) {
if (nullptr == mainWidget_) {
return 1.0;
}
return value / mainWidget_->devicePixelRatioF();
}
double FramelessDelegateWin::UnDpiScale(double value) {
if (nullptr == mainWidget_) {
return 1.0;
}
return value * mainWidget_->devicePixelRatioF();
}
void FramelessDelegateWin::CheckMonitorChanged() {
if (nullptr == mainWidget_) {
return;
}
auto hwnd = reinterpret_cast<HWND>(mainWidget_->winId());
auto newMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
if (newMonitor != monitor_) {
monitor_ = newMonitor;
auto ret = GetScreenNativeWorkRect(hwnd);
if ( !!ret)
workRect_ = ret;
}
}
void FramelessDelegateWin::ShowSystemMenu(const QPoint& pos) {
auto hwnd = (HWND)mainWidget_->winId();
HMENU menu = GetSystemMenu(hwnd, false);
if (menu) {
MENUITEMINFO mii;
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STATE;
mii.fType = 0;
mii.fState = MF_ENABLED;
SetMenuItemInfo(menu, SC_RESTORE, FALSE, &mii);
SetMenuItemInfo(menu, SC_MAXIMIZE, FALSE, &mii);
SetMenuItemInfo(menu, SC_MINIMIZE, FALSE, &mii);
// update the options
mii.fState = MF_DISABLED;
if (mainWidget_->maximumSize() == mainWidget_->minimumSize() || mainWidget_->isMaximized())
SetMenuItemInfo(menu, SC_SIZE, FALSE, &mii);
else
SetMenuItemInfo(menu, SC_SIZE, TRUE, &mii);
if (!mainWidget_->isMaximized())
SetMenuItemInfo(menu, SC_MOVE, TRUE, &mii);
else
SetMenuItemInfo(menu, SC_MOVE, FALSE, &mii);
mii.fState = MF_GRAYED;
WINDOWPLACEMENT wp;
GetWindowPlacement(hwnd, &wp);
switch (wp.showCmd) {
case SW_SHOWMAXIMIZED:
SetMenuItemInfo(menu, SC_MAXIMIZE, FALSE, &mii);
SetMenuDefaultItem(menu, SC_CLOSE, FALSE);
break;
case SW_SHOWMINIMIZED:
SetMenuItemInfo(menu, SC_MINIMIZE, FALSE, &mii);
SetMenuDefaultItem(menu, SC_RESTORE, FALSE);
break;
case SW_SHOWNORMAL:
{
if (mainWidget_->maximumSize() == mainWidget_->minimumSize()) {
SetMenuItemInfo(menu, SC_MAXIMIZE, FALSE, &mii);
}
if (!mainWidget_->windowFlags().testFlag(Qt::WindowMinimizeButtonHint)) {
SetMenuItemInfo(menu, SC_MINIMIZE, FALSE, &mii);
}
SetMenuItemInfo(menu, SC_RESTORE, FALSE, &mii);
SetMenuDefaultItem(menu, SC_CLOSE, FALSE);
break;
}
}
auto devicePixelRatio = qApp->devicePixelRatio();
auto globalPos = pos;
globalPos.rx() *= devicePixelRatio;
globalPos.ry() *= devicePixelRatio;
BOOL cmd = TrackPopupMenuEx(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD, globalPos.x(), globalPos.y(), hwnd, nullptr);
if (cmd)
PostMessage(hwnd, WM_SYSCOMMAND, cmd, 0);
}
}
void FramelessDelegateWin::SetNativeWindowLong() {
if (nullptr == mainWidget_) {
return;
}
HWND hwnd = reinterpret_cast<HWND>(mainWidget_->winId());
unsigned long style = BorderlessFlag;
if (!mainWidget_->windowFlags().testFlag(Qt::WindowMinimizeButtonHint) ||
mainWidget_->maximumSize() != QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) {
style &= ~WS_MINIMIZEBOX;
}
if (!mainWidget_->windowFlags().testFlag(Qt::WindowMaximizeButtonHint)) {
style &= ~WS_MAXIMIZEBOX;
}
::SetWindowLongPtr(hwnd, GWL_STYLE, style);
if (IsCompositionEnabled()) {
ExtendFrameIntoClientArea(mainWidget_->windowHandle(), 1, 0, 0, 0);
}
RECT rect;
GetWindowRect(hwnd, &rect);
SetWindowPos(
hwnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
bool FramelessDelegateWin::OnNCTitTest(MSG* msg, long* result) {
#else
bool FramelessDelegateWin::OnNCTitTest(MSG* msg, qintptr* result) {
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
auto [x, y] = QCursor::pos();
#else
auto p = QCursor::pos();
auto x = p.x();
auto y = p.y();
#endif
auto borderX = GetSystemMetrics(SM_CXPADDEDBORDER);
auto borderY = GetSystemMetrics(SM_CXPADDEDBORDER);
bool maxSized = mainWidget_->isMaximized() || mainWidget_->isFullScreen();
if (maxSized) {
borderX = 0;
borderY = 0;
}
if (mainWidget_->minimumSize() != mainWidget_->maximumSize()) {
auto rect = mainWidget_->geometry();
if (!maxSized && x >= rect.left() && x <= rect.left() + borderX) {
if (y >= rect.top() && y <= rect.top() + borderY) {
*result = HTTOPLEFT;
return true;
}
if (y > rect.top() + borderY && y < rect.bottom() - borderY) {
*result = HTLEFT;
return true;
}
if (y >= rect.bottom() - borderY && y <= rect.bottom()) {
*result = HTBOTTOMLEFT;
return true;
}
} else if (x > rect.left() + borderX && x < rect.right() - borderX) {
if (!maxSized && y >= rect.top() && y <= rect.top() + borderY) {
*result = HTTOP;
return true;
}
if (y > rect.top() + borderY && y < rect.top() + borderY) {
*result = HTCAPTION;
return true;
}
if (!maxSized && y >= rect.bottom() - borderY && y <= rect.bottom()) {
*result = HTBOTTOM;
return true;
}
} else if (!maxSized && x >= rect.right() - borderX && x <= rect.right()) {
if (y >= rect.top() && y <= rect.top() + borderY) {
*result = HTTOPRIGHT;
return true;
}
if (y > rect.top() + borderY && y < rect.bottom() - borderY) {
*result = HTRIGHT;
return true;
}
if (y >= rect.bottom() - borderY && y <= rect.bottom()) {
*result = HTBOTTOMRIGHT;
return true;
}
} else if (!maxSized) {
*result = HTNOWHERE;
return true;
}
}
auto localPos = mainWidget_->mapFromGlobal(QPoint(x, y));
for (auto w : moveBars_) {
auto child = mainWidget_->childAt(localPos);
if (!child)
continue;
if (child == w) {
auto pos = w->mapFrom(mainWidget_, localPos);
auto title = qobject_cast<FrameTitleBar*>(w);
/*if (title) {
if (title->iconIsVisible() && title->doIconRect().contains(pos)) {
*result = HTSYSMENU;
return true;
}
}*/
*result = HTCAPTION;
return true;
} else {
auto it = std::find_if(w->children().begin(),
w->children().end(),
[&](const auto& obj) {
if (obj->isWidgetType()) {
return obj == child;
}
return false;
});
if (it != w->children().end() && w->metaObject()->className() != QString("QWidget") &&
w->metaObject()->className() != QString("QLabel")) {
// if ((*it)->property("qcustomui_maximumSizeButton").isValid())
//{
// *result = HTMAXBUTTON;
// return false;
// }
//*result = HTCLIENT;
//return false;
}
}
}
return false;
}