491 lines
15 KiB
C++
491 lines
15 KiB
C++
|
#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;
|
||
|
}
|