15#ifdef FRAMELESS_USE_NATIVE
26static int s_nBorderWidth = 5;
27static int s_nTitleHeight = 30;
29class FramelessWidgetData;
31class FramelessHelperPrivate
34 FramelessHelperPrivate();
35 ~FramelessHelperPrivate();
37 bool isCaptionClassName(
const char *
name);
39 QHash<QWidget *, FramelessWidgetData *> m_widgetDataHash;
40 QList<QString> m_captionClassNameList;
41 bool m_bWidgetResizable =
true;
42 bool m_bWidgetMovable =
true;
46FramelessHelperPrivate::FramelessHelperPrivate()
48 m_captionClassNameList <<
"QWidget";
51FramelessHelperPrivate::~FramelessHelperPrivate()
55bool FramelessHelperPrivate::isCaptionClassName(
const char *
name)
57 foreach (
const QString &cn, m_captionClassNameList) {
58 if (cn.compare(QLatin1String(
name)) == 0) {
65class FramelessWidgetData
68 explicit FramelessWidgetData(FramelessHelperPrivate *_d, QWidget *widget);
69 virtual ~FramelessWidgetData();
72 virtual bool handleWidgetEvent(QEvent *event);
74 void handleWindowStateChangeEvent();
76 FramelessHelperPrivate *d;
80FramelessWidgetData::FramelessWidgetData(FramelessHelperPrivate *_d, QWidget *widget)
87FramelessWidgetData::~FramelessWidgetData()
91QWidget *FramelessWidgetData::widget()
96bool FramelessWidgetData::handleWidgetEvent(QEvent *event)
102void FramelessWidgetData::handleWindowStateChangeEvent()
104 Qt::WindowStates state = m_pWidget->windowState();
105 bool resizeDisable = state.testFlag(Qt::WindowFullScreen) || state.testFlag(Qt::WindowMaximized);
106 foreach (QSizeGrip *sg, m_pWidget->findChildren<QSizeGrip *>()) {
107 sg->setVisible(!resizeDisable);
111#ifdef FRAMELESS_USE_NATIVE
116#define WM_DPICHANGED 0x02E0
121class FramelessWidgetDataNativeWin :
public FramelessWidgetData
124 explicit FramelessWidgetDataNativeWin(FramelessHelperPrivate *_d, QWidget *widget);
125 virtual ~FramelessWidgetDataNativeWin();
127 qreal devicePixelRatio();
129 bool handleNativeWindowsMessage(MSG *msg,
QTRESULT *result);
131 bool handleNonClinetCalcSize(MSG *msg,
QTRESULT *result);
132 bool handleNonClientHitTest(MSG *msg,
QTRESULT *result);
134 void setFrameChanged(MSG *msg);
136 UINT m_dpi[2] = {96, 96};
139FramelessWidgetDataNativeWin::FramelessWidgetDataNativeWin(FramelessHelperPrivate *_d, QWidget *widget)
140 : FramelessWidgetData(_d, widget)
142 HWND hwnd = (HWND)m_pWidget->winId();
143 DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE);
144 ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
151FramelessWidgetDataNativeWin::~FramelessWidgetDataNativeWin()
155qreal FramelessWidgetDataNativeWin::devicePixelRatio()
157#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
158 return m_pWidget->windowHandle()->screen()->devicePixelRatio();
160 return m_pWidget->screen()->devicePixelRatio();
164bool FramelessWidgetDataNativeWin::handleNativeWindowsMessage(MSG *msg,
QTRESULT *result)
166 switch (msg->message) {
168 return handleNonClinetCalcSize(msg, result);
170 return handleNonClientHitTest(msg, result);
174 m_dpi[1] = HIWORD(msg->wParam);
180 if (m_dpi[0] != m_dpi[1]) {
183 setFrameChanged(msg);
191bool FramelessWidgetDataNativeWin::handleNonClinetCalcSize(MSG *msg,
QTRESULT *result)
202 NCCALCSIZE_PARAMS *pncsp =
reinterpret_cast<NCCALCSIZE_PARAMS *
>(msg->lParam);
204 const int originalTop = pncsp->rgrc[0].top;
205 const RECT originalRect = pncsp->rgrc[0];
208 const auto ret = ::DefWindowProc(msg->hwnd, WM_NCCALCSIZE, msg->wParam, msg->lParam);
214 pncsp->rgrc[0].top = originalTop;
216 bool isMaximized = GetWindowStyle(msg->hwnd) & WS_MAXIMIZE;
218#ifdef SM_CXPADDEDBORDER
219 int border = GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(
SM_CXPADDEDBORDER);
221 int border = GetSystemMetrics(SM_CXSIZEFRAME);
223 pncsp->rgrc[0].top += border;
225 pncsp->rgrc[0] = originalRect;
230bool FramelessWidgetDataNativeWin::handleNonClientHitTest(MSG *msg,
QTRESULT *result)
233 if (m_pWidget->isFullScreen()) {
238 int borderWidth = s_nBorderWidth;
244 qreal pixelRatio = devicePixelRatio();
245 QPoint pos = m_pWidget->mapFromGlobal(QPoint(x, y) / pixelRatio);
246 int w = m_pWidget->width();
247 int h = m_pWidget->height();
249 bool left = pos.x() < borderWidth;
250 bool right = pos.x() > w - borderWidth;
251 bool top = pos.y() < borderWidth;
252 bool bottom = pos.y() > h - borderWidth;
254 if (d->m_bWidgetResizable) {
257 }
else if (top && right) {
258 *result = HTTOPRIGHT;
259 }
else if (bottom && left) {
260 *result = HTBOTTOMLEFT;
261 }
else if (bottom && right) {
262 *result = HTBOTTOMRIGHT;
273 if (*result != HTNOWHERE) {
278 if (d->m_bWidgetMovable && pos.y() < s_nTitleHeight) {
279 QWidget *child = m_pWidget->childAt(pos);
280 if (!child || d->isCaptionClassName(child->metaObject()->className())) {
288void FramelessWidgetDataNativeWin::setFrameChanged(MSG *msg)
296 GetWindowRect(msg->hwnd, &rcClient);
301 SetWindowPos(msg->hwnd, NULL, rcClient.left, rcClient.top, rcClient.right - rcClient.left,
302 rcClient.bottom - rcClient.top, SWP_FRAMECHANGED);
317 void update(
const QPoint &gMousePos,
const QRect &frameRect);
323 bool m_bOnBottomEdge;
324 bool m_bOnTopLeftEdge;
325 bool m_bOnBottomLeftEdge;
326 bool m_bOnTopRightEdge;
327 bool m_bOnBottomRightEdge;
330FramelessCursor::FramelessCursor()
335void FramelessCursor::reset()
338 m_bOnLeftEdge =
false;
339 m_bOnRightEdge =
false;
340 m_bOnTopEdge =
false;
341 m_bOnBottomEdge =
false;
342 m_bOnTopLeftEdge =
false;
343 m_bOnBottomLeftEdge =
false;
344 m_bOnTopRightEdge =
false;
345 m_bOnBottomRightEdge =
false;
348void FramelessCursor::update(
const QPoint &gMousePos,
const QRect &frameRect)
350 int globalMouseX = gMousePos.x();
351 int globalMouseY = gMousePos.y();
352 int frameX = frameRect.x();
353 int frameY = frameRect.y();
354 int frameWidth = frameRect.width();
355 int frameHeight = frameRect.height();
357 m_bOnLeftEdge = (globalMouseX >= frameX && globalMouseX <= frameX + s_nBorderWidth);
358 m_bOnRightEdge = (globalMouseX >= frameX + frameWidth - s_nBorderWidth && globalMouseX <= frameX + frameWidth);
359 m_bOnTopEdge = (globalMouseY >= frameY && globalMouseY <= frameY + s_nBorderWidth);
360 m_bOnBottomEdge = (globalMouseY >= frameY + frameHeight - s_nBorderWidth && globalMouseY <= frameY + frameHeight);
362 m_bOnTopLeftEdge = m_bOnTopEdge && m_bOnLeftEdge;
363 m_bOnBottomLeftEdge = m_bOnBottomEdge && m_bOnLeftEdge;
364 m_bOnTopRightEdge = m_bOnTopEdge && m_bOnRightEdge;
365 m_bOnBottomRightEdge = m_bOnBottomEdge && m_bOnRightEdge;
367 m_bOnEdges = m_bOnLeftEdge || m_bOnRightEdge || m_bOnTopEdge || m_bOnBottomEdge;
372class FramelessWidgetDataQt :
public FramelessWidgetData
375 explicit FramelessWidgetDataQt(FramelessHelperPrivate *d, QWidget *widget);
376 ~FramelessWidgetDataQt();
378 bool handleWidgetEvent(QEvent *event)
override;
380 void updateCursorShape(
const QPoint &gMousePos);
381 void resizeWidget(
const QPoint &gMousePos);
382 void moveWidget(
const QPoint &gMousePos);
384 bool handleMousePressEvent(QMouseEvent *event);
385 bool handleMouseReleaseEvent(QMouseEvent *event);
386 bool handleMouseMoveEvent(QMouseEvent *event);
387 bool handleLeaveEvent(QEvent *event);
388 bool handleHoverMoveEvent(QHoverEvent *event);
389 bool handleDoubleClickedMouseEvent(QMouseEvent *event);
392 FramelessCursor m_pressedMousePos;
393 FramelessCursor m_moveMousePos;
394 bool m_bLeftButtonPressed;
395 bool m_bCursorShapeChanged;
396 bool m_bLeftButtonTitlePressed;
399FramelessWidgetDataQt::FramelessWidgetDataQt(FramelessHelperPrivate *_d, QWidget *widget)
400 : FramelessWidgetData(_d, widget)
402 m_bLeftButtonPressed =
false;
403 m_bCursorShapeChanged =
false;
404 m_bLeftButtonTitlePressed =
false;
406 m_pWidget->setMouseTracking(
true);
407 m_pWidget->setAttribute(Qt::WA_Hover,
true);
410FramelessWidgetDataQt::~FramelessWidgetDataQt()
412 m_pWidget->setMouseTracking(
false);
413 m_pWidget->setAttribute(Qt::WA_Hover,
false);
416bool FramelessWidgetDataQt::handleWidgetEvent(QEvent *event)
418 switch (event->type()) {
419 case QEvent::MouseButtonPress:
420 return handleMousePressEvent(
static_cast<QMouseEvent *
>(event));
421 case QEvent::MouseButtonRelease:
422 return handleMouseReleaseEvent(
static_cast<QMouseEvent *
>(event));
423 case QEvent::MouseMove:
424 return handleMouseMoveEvent(
static_cast<QMouseEvent *
>(event));
426 return handleLeaveEvent(event);
427 case QEvent::HoverMove:
428 return handleHoverMoveEvent(
static_cast<QHoverEvent *
>(event));
429 case QEvent::MouseButtonDblClick:
430 return handleDoubleClickedMouseEvent(
static_cast<QMouseEvent *
>(event));
437void FramelessWidgetDataQt::updateCursorShape(
const QPoint &gMousePos)
439 if (m_pWidget->isFullScreen() || m_pWidget->isMaximized()) {
440 if (m_bCursorShapeChanged) {
441 m_pWidget->unsetCursor();
446 m_moveMousePos.update(gMousePos, m_pWidget->frameGeometry());
448 if (m_moveMousePos.m_bOnTopLeftEdge || m_moveMousePos.m_bOnBottomRightEdge) {
449 m_pWidget->setCursor(Qt::SizeFDiagCursor);
450 m_bCursorShapeChanged =
true;
451 }
else if (m_moveMousePos.m_bOnTopRightEdge || m_moveMousePos.m_bOnBottomLeftEdge) {
452 m_pWidget->setCursor(Qt::SizeBDiagCursor);
453 m_bCursorShapeChanged =
true;
454 }
else if (m_moveMousePos.m_bOnLeftEdge || m_moveMousePos.m_bOnRightEdge) {
455 m_pWidget->setCursor(Qt::SizeHorCursor);
456 m_bCursorShapeChanged =
true;
457 }
else if (m_moveMousePos.m_bOnTopEdge || m_moveMousePos.m_bOnBottomEdge) {
458 m_pWidget->setCursor(Qt::SizeVerCursor);
459 m_bCursorShapeChanged =
true;
461 if (m_bCursorShapeChanged) {
462 m_pWidget->unsetCursor();
463 m_bCursorShapeChanged =
false;
468void FramelessWidgetDataQt::resizeWidget(
const QPoint &gMousePos)
470 QRect origRect = m_pWidget->frameGeometry();
472 int left = origRect.left();
473 int top = origRect.top();
474 int right = origRect.right();
475 int bottom = origRect.bottom();
477 origRect.getCoords(&left, &top, &right, &bottom);
479 int minWidth = m_pWidget->minimumWidth();
480 int minHeight = m_pWidget->minimumHeight();
482 if (m_pressedMousePos.m_bOnTopLeftEdge) {
483 left = gMousePos.x();
485 }
else if (m_pressedMousePos.m_bOnBottomLeftEdge) {
486 left = gMousePos.x();
487 bottom = gMousePos.y();
488 }
else if (m_pressedMousePos.m_bOnTopRightEdge) {
489 right = gMousePos.x();
491 }
else if (m_pressedMousePos.m_bOnBottomRightEdge) {
492 right = gMousePos.x();
493 bottom = gMousePos.y();
494 }
else if (m_pressedMousePos.m_bOnLeftEdge) {
495 left = gMousePos.x();
496 }
else if (m_pressedMousePos.m_bOnRightEdge) {
497 right = gMousePos.x();
498 }
else if (m_pressedMousePos.m_bOnTopEdge) {
500 }
else if (m_pressedMousePos.m_bOnBottomEdge) {
501 bottom = gMousePos.y();
504 QRect newRect(QPoint(left, top), QPoint(right, bottom));
506 if (newRect.isValid()) {
507 if (minWidth > newRect.width()) {
508 if (left != origRect.left()) {
509 newRect.setLeft(origRect.left());
511 newRect.setRight(origRect.right());
514 if (minHeight > newRect.height()) {
515 if (top != origRect.top()) {
516 newRect.setTop(origRect.top());
518 newRect.setBottom(origRect.bottom());
522 m_pWidget->setGeometry(newRect);
526void FramelessWidgetDataQt::moveWidget(
const QPoint &gMousePos)
528 m_pWidget->move(gMousePos - m_ptDragPos);
531bool FramelessWidgetDataQt::handleMousePressEvent(QMouseEvent *event)
533 if (event->button() == Qt::LeftButton) {
534 m_bLeftButtonPressed =
true;
535 m_bLeftButtonTitlePressed =
event->pos().y() < s_nTitleHeight;
537 QRect frameRect = m_pWidget->frameGeometry();
538#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
542 m_pressedMousePos.update(event->globalPosition().toPoint(), frameRect);
543 m_ptDragPos =
event->globalPosition().toPoint() - frameRect.topLeft();
546 if (m_pressedMousePos.m_bOnEdges) {
547 if (m_pWidget->isMaximized() || m_pWidget->isFullScreen()) {
555bool FramelessWidgetDataQt::handleMouseReleaseEvent(QMouseEvent *event)
557 if (event->button() == Qt::LeftButton) {
558 m_bLeftButtonPressed =
false;
559 m_bLeftButtonTitlePressed =
false;
560 m_pressedMousePos.reset();
565bool FramelessWidgetDataQt::handleMouseMoveEvent(QMouseEvent *event)
567 if (m_bLeftButtonPressed) {
568 if (d->m_bWidgetResizable && m_pressedMousePos.m_bOnEdges) {
570 if (m_pWidget->isMaximized() || m_pWidget->isFullScreen()) {
573#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
576 resizeWidget(event->globalPosition().toPoint());
579 }
else if (d->m_bWidgetMovable && m_bLeftButtonTitlePressed) {
580 if (m_pWidget->isFullScreen()) {
583 if (m_pWidget->isMaximized()) {
591 QRect normalGeometry = m_pWidget->normalGeometry();
592 m_pWidget->showNormal();
593#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
596 QPoint p =
event->globalPosition().toPoint();
599 p.rx() -= (normalGeometry.width() / 2);
602 m_ptDragPos = QPoint(normalGeometry.width() / 2, 10);
605#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
608 moveWidget(event->globalPosition().toPoint());
613 }
else if (d->m_bWidgetResizable) {
614#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
617 updateCursorShape(event->globalPosition().toPoint());
623bool FramelessWidgetDataQt::handleLeaveEvent(QEvent *event)
626 if (!m_bLeftButtonPressed) {
627 m_pWidget->unsetCursor();
633bool FramelessWidgetDataQt::handleHoverMoveEvent(QHoverEvent *event)
635 if (d->m_bWidgetResizable) {
636#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
637 updateCursorShape(m_pWidget->mapToGlobal(event->pos()));
639 updateCursorShape(m_pWidget->mapToGlobal(event->position().toPoint()));
645bool FramelessWidgetDataQt::handleDoubleClickedMouseEvent(QMouseEvent *event)
647 if (event->button() == Qt::LeftButton) {
648 if (m_pWidget->windowFlags() & Qt::WindowMaximizeButtonHint) {
649 bool titlePressed =
event->pos().y() < s_nTitleHeight;
650 if (titlePressed && !m_pWidget->isFullScreen()) {
651 if (m_pWidget->isMaximized()) {
652 m_pWidget->showNormal();
654 m_pWidget->showMaximized();
668 , d(new FramelessHelperPrivate)
671 if (QWidget *w = qobject_cast<QWidget *>(parent)) {
672 w->setWindowFlags(w->windowFlags() | Qt::FramelessWindowHint);
676 qApp->installNativeEventFilter(
this);
681 qApp->removeNativeEventFilter(
this);
682 QList<QWidget *> keys = d->m_widgetDataHash.keys();
683 int size = keys.size();
685 for (
int i = 0; i < size; ++i) {
686 keys[i]->removeEventFilter(
this);
687 delete d->m_widgetDataHash.take(keys[i]);
694 if (!d->m_widgetDataHash.contains(w)) {
695#ifdef FRAMELESS_USE_NATIVE
697 FramelessWidgetDataNativeWin *data =
new FramelessWidgetDataNativeWin(d, w);
699 FramelessWidgetData *data =
new FramelessWidgetData(d, w);
702 FramelessWidgetDataQt *data =
new FramelessWidgetDataQt(d, w);
704 d->m_widgetDataHash.insert(w, data);
705 w->installEventFilter(
this);
707 connect(w, &QWidget::destroyed,
this, [
this, w]() {
716 FramelessWidgetData *data = d->m_widgetDataHash.take(w);
718 w->removeEventFilter(
this);
725 return d->m_bWidgetResizable;
730 d->m_bWidgetResizable = resizable;
735 return d->m_bWidgetMovable;
740 d->m_bWidgetMovable = movable;
745 return s_nTitleHeight;
751 s_nTitleHeight = height;
757 return s_nBorderWidth;
763 s_nBorderWidth = width;
769 if (!d->m_captionClassNameList.contains(
name)) {
770 d->m_captionClassNameList.append(
name);
776 QWidget *widget =
static_cast<QWidget *
>(object);
777 FramelessWidgetData *data = d->m_widgetDataHash.value(widget);
779 return QObject::eventFilter(
object, event);
781 switch (event->type()) {
782 case QEvent::WindowTitleChange:
785 case QEvent::WindowIconChange:
788 case QEvent::WindowStateChange:
789 data->handleWindowStateChangeEvent();
793 return data->handleWidgetEvent(event);
795 return QObject::eventFilter(
object, event);
801 if (eventType ==
"windows_generic_MSG") {
802 MSG *msg =
reinterpret_cast<MSG *
>(message);
803 QWidget *widget = QWidget::find(
reinterpret_cast<WId
>(msg->hwnd));
804 FramelessWidgetData *data = d->m_widgetDataHash.value(widget);
805 if (data ==
nullptr) {
808#ifndef FRAMELESS_USE_NATIVE
809 switch (msg->message) {
810 case WM_GETMINMAXINFO: {
812 if (widget->isMaximized()) {
813 QScreen *screen = widget->screen();
814 qreal ratio = screen->devicePixelRatio();
815 const QRect rc = screen->availableGeometry();
816 MINMAXINFO *p = (MINMAXINFO *)(msg->lParam);
817 p->ptMaxPosition.x = 0;
818 p->ptMaxPosition.y = 0;
819 p->ptMaxSize.x = rc.width() * ratio;
820 p->ptMaxSize.y = rc.height() * ratio;
821 *result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
827 FramelessWidgetDataNativeWin *winData =
reinterpret_cast<FramelessWidgetDataNativeWin *
>(data);
828 if (winData ==
nullptr) {
831 return winData->handleNativeWindowsMessage(msg, result);
void removeWidget(QWidget *w)
void windowTitleChanged(const QString &title)
FramelessHelper(QObject *parent)
void setWidgetResizable(bool resizable)
void windowStateChanged(Qt::WindowStates state)
bool widgetMovable() const
void addCaptionClassName(const QString &name)
bool widgetResizable() const
void setWidgetMovable(bool movable)
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
void addWidget(QWidget *w)
virtual bool eventFilter(QObject *object, QEvent *event)
void setTitleHeight(int height)
void windowIconChanged(const QIcon &icon)
void setBorderWidth(int width)
#define QTRESULT
Copyleft (C) 2023-2024 maminjie canpool@163.com SPDX-License-Identifier: MIT
QPoint globalMousePos(const QMouseEvent *event)
#define SM_CXPADDEDBORDER
#define GET_X_LPARAM(lp)
Copyright (C) 2023-2024 maminjie canpool@163.com SPDX-License-Identifier: Apache-2....