diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6e7f2d3..e2bc683 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -26,6 +26,7 @@ if(FRAMELESSHELPER_BUILD_WIDGETS AND TARGET Qt${QT_VERSION_MAJOR}::Widgets) add_subdirectory(widget) add_subdirectory(mainwindow) add_subdirectory(openglwidget) + add_subdirectory(dialog) endif() if(FRAMELESSHELPER_BUILD_QUICK AND TARGET Qt${QT_VERSION_MAJOR}::Quick AND ${QT_VERSION_MAJOR} GREATER_EQUAL 6) diff --git a/examples/dialog/CMakeLists.txt b/examples/dialog/CMakeLists.txt new file mode 100644 index 0000000..cf6c5db --- /dev/null +++ b/examples/dialog/CMakeLists.txt @@ -0,0 +1,52 @@ +#[[ + MIT License + + Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +]] + +set(SOURCES + dialog.h + dialog.cpp + main.cpp +) + +if(WIN32) + enable_language(RC) + list(APPEND SOURCES ../example.rc ../example.manifest) +endif() + +add_executable(Dialog ${SOURCES}) + +target_link_libraries(Dialog PRIVATE + Qt${QT_VERSION_MAJOR}::Widgets + FramelessHelper::Widgets +) + +target_compile_definitions(Dialog PRIVATE + QT_NO_KEYWORDS +) + +include(../../src/core/cmakehelper.cmake) +setup_gui_app(Dialog) +setup_compile_params(Dialog) +if(FRAMELESSHELPER_EXAMPLES_DEPLOYQT) + deploy_qt_runtime(Dialog) +endif() diff --git a/examples/dialog/dialog.cpp b/examples/dialog/dialog.cpp new file mode 100644 index 0000000..d0b82e4 --- /dev/null +++ b/examples/dialog/dialog.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "dialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +FRAMELESSHELPER_USE_NAMESPACE + +using namespace Global; + +Dialog::Dialog(QWidget *parent) : FramelessDialog(parent) +{ + setupUi(); +} + +Dialog::~Dialog() = default; + +void Dialog::setupUi() +{ + setWindowTitle(tr("Qt Dialog demo")); + setWindowIcon(QFileIconProvider().icon(QFileIconProvider::Computer)); + + titleBar = new StandardTitleBar(this); + titleBar->setWindowIconVisible(true); + titleBar->maximizeButton()->hide(); + + label = new QLabel(tr("Find &what:")); + lineEdit = new QLineEdit; + label->setBuddy(lineEdit); + + caseCheckBox = new QCheckBox(tr("Match &case")); + fromStartCheckBox = new QCheckBox(tr("Search from &start")); + fromStartCheckBox->setChecked(true); + + findButton = new QPushButton(tr("&Find")); + findButton->setDefault(true); + + moreButton = new QPushButton(tr("&More")); + moreButton->setCheckable(true); + moreButton->setAutoDefault(false); + + extension = new QWidget; + + wholeWordsCheckBox = new QCheckBox(tr("&Whole words")); + backwardCheckBox = new QCheckBox(tr("Search &backward")); + searchSelectionCheckBox = new QCheckBox(tr("Search se&lection")); + + buttonBox = new QDialogButtonBox(Qt::Vertical); + buttonBox->addButton(findButton, QDialogButtonBox::ActionRole); + buttonBox->addButton(moreButton, QDialogButtonBox::ActionRole); + + connect(moreButton, &QPushButton::toggled, extension, &QWidget::setVisible); + + QVBoxLayout *extensionLayout = new QVBoxLayout; + extensionLayout->setContentsMargins(0, 0, 0, 0); + extensionLayout->addWidget(wholeWordsCheckBox); + extensionLayout->addWidget(backwardCheckBox); + extensionLayout->addWidget(searchSelectionCheckBox); + extension->setLayout(extensionLayout); + + QHBoxLayout *topLeftLayout = new QHBoxLayout; + topLeftLayout->addWidget(label); + topLeftLayout->addWidget(lineEdit); + + QVBoxLayout *leftLayout = new QVBoxLayout; + leftLayout->addLayout(topLeftLayout); + leftLayout->addWidget(caseCheckBox); + leftLayout->addWidget(fromStartCheckBox); + + QGridLayout *controlsLayout = new QGridLayout; + controlsLayout->setContentsMargins(11, 11, 11, 11); + controlsLayout->addLayout(leftLayout, 0, 0); + controlsLayout->addWidget(buttonBox, 0, 1); + controlsLayout->addWidget(extension, 1, 0, 1, 2); + controlsLayout->setRowStretch(2, 1); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSizeConstraint(QLayout::SetFixedSize); + mainLayout->addWidget(titleBar); + mainLayout->addLayout(controlsLayout); + + setLayout(mainLayout); + + extension->hide(); + + FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); + helper->setTitleBarWidget(titleBar); + helper->setSystemButton(titleBar->minimizeButton(), SystemButtonType::Minimize); + helper->setSystemButton(titleBar->maximizeButton(), SystemButtonType::Maximize); + helper->setSystemButton(titleBar->closeButton(), SystemButtonType::Close); + FramelessWidgetsHelperPrivate::get(helper)->setProperty(FRAMELESSHELPER_BYTEARRAY_LITERAL("FRAMELESSHELPER_DONT_OVERRIDE_CURSOR"), true); +} diff --git a/examples/dialog/dialog.h b/examples/dialog/dialog.h new file mode 100644 index 0000000..e4d2439 --- /dev/null +++ b/examples/dialog/dialog.h @@ -0,0 +1,46 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#pragma once + +#include + +QT_BEGIN_NAMESPACE +class QCheckBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QPushButton; +QT_END_NAMESPACE + +FRAMELESSHELPER_BEGIN_NAMESPACE +class StandardTitleBar; +FRAMELESSHELPER_END_NAMESPACE + +class Dialog : public FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessDialog) +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(Dialog) + +public: + explicit Dialog(QWidget *parent = nullptr); + ~Dialog() override; + +private: + void setupUi(); + +private: + FRAMELESSHELPER_PREPEND_NAMESPACE(StandardTitleBar) *titleBar = nullptr; + QLabel *label = nullptr; + QLineEdit *lineEdit = nullptr; + QCheckBox *caseCheckBox = nullptr; + QCheckBox *fromStartCheckBox = nullptr; + QCheckBox *wholeWordsCheckBox = nullptr; + QCheckBox *searchSelectionCheckBox = nullptr; + QCheckBox *backwardCheckBox = nullptr; + QDialogButtonBox *buttonBox = nullptr; + QPushButton *findButton = nullptr; + QPushButton *moreButton = nullptr; + QWidget *extension = nullptr; +}; diff --git a/examples/dialog/main.cpp b/examples/dialog/main.cpp new file mode 100644 index 0000000..a157e2d --- /dev/null +++ b/examples/dialog/main.cpp @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "dialog.h" + +FRAMELESSHELPER_USE_NAMESPACE + +int main(int argc, char *argv[]) +{ + std::setlocale(LC_ALL, "en_US.UTF-8"); + + // Not necessary, but better call this function, before the construction + // of any Q(Core|Gui)Application instances. + FramelessHelper::Widgets::initialize(); + + QApplication application(argc, argv); + + // Must be called after QGuiApplication has been constructed, we are using + // some private functions from QPA which won't be available until there's + // a QGuiApplication instance. + FramelessHelper::Core::setApplicationOSThemeAware(true, false); + + FramelessConfig::instance()->set(Global::Option::WindowUseRoundCorners); + FramelessConfig::instance()->set(Global::Option::EnableBlurBehindWindow); + + Dialog dialog; + dialog.show(); + + const int exec = QCoreApplication::exec(); + + FramelessHelper::Widgets::uninitialize(); + + return exec; +} diff --git a/include/FramelessHelper/Core/framelesshelpercore_global.h b/include/FramelessHelper/Core/framelesshelpercore_global.h index 623e7e8..f772f55 100644 --- a/include/FramelessHelper/Core/framelesshelpercore_global.h +++ b/include/FramelessHelper/Core/framelesshelpercore_global.h @@ -452,6 +452,10 @@ using GetWindowIdCallback = std::function; using ShouldIgnoreMouseEventsCallback = std::function; using ShowSystemMenuCallback = std::function; using GetCurrentApplicationTypeCallback = std::function; +using SetPropertyCallback = std::function; +using GetPropertyCallback = std::function; +using SetCursorCallback = std::function; +using UnsetCursorCallback = std::function; struct SystemParameters { @@ -477,6 +481,10 @@ struct SystemParameters ShouldIgnoreMouseEventsCallback shouldIgnoreMouseEvents = nullptr; ShowSystemMenuCallback showSystemMenu = nullptr; GetCurrentApplicationTypeCallback getCurrentApplicationType = nullptr; + SetPropertyCallback setProperty = nullptr; + GetPropertyCallback getProperty = nullptr; + SetCursorCallback setCursor = nullptr; + UnsetCursorCallback unsetCursor = nullptr; [[nodiscard]] inline bool isValid() const { @@ -502,6 +510,10 @@ struct SystemParameters Q_ASSERT(shouldIgnoreMouseEvents); Q_ASSERT(showSystemMenu); Q_ASSERT(getCurrentApplicationType); + Q_ASSERT(setProperty); + Q_ASSERT(getProperty); + Q_ASSERT(setCursor); + Q_ASSERT(unsetCursor); return (getWindowFlags && setWindowFlags && getWindowSize && setWindowSize && getWindowPosition && setWindowPosition && getWindowScreen && isWindowFixedSize && setWindowFixedSize @@ -509,7 +521,8 @@ struct SystemParameters && windowToScreen && screenToWindow && isInsideSystemButtons && isInsideTitleBarDraggableArea && getWindowDevicePixelRatio && setSystemButtonState && getWindowId && shouldIgnoreMouseEvents - && showSystemMenu && getCurrentApplicationType); + && showSystemMenu && getCurrentApplicationType && setProperty + && getProperty && setCursor && unsetCursor); } }; diff --git a/include/FramelessHelper/Quick/private/framelessquickhelper_p.h b/include/FramelessHelper/Quick/private/framelessquickhelper_p.h index 01aa33f..117588a 100644 --- a/include/FramelessHelper/Quick/private/framelessquickhelper_p.h +++ b/include/FramelessHelper/Quick/private/framelessquickhelper_p.h @@ -71,6 +71,9 @@ public: Q_NODISCARD bool isBlurBehindWindowEnabled() const; void setBlurBehindWindowEnabled(const bool value, const QColor &color); + void setProperty(const QByteArray &name, const QVariant &value); + Q_NODISCARD QVariant getProperty(const QByteArray &name, const QVariant &defaultValue = {}); + protected: Q_NODISCARD bool eventFilter(QObject *object, QEvent *event) override; diff --git a/include/FramelessHelper/Widgets/FramelessDialog b/include/FramelessHelper/Widgets/FramelessDialog new file mode 100644 index 0000000..1291fea --- /dev/null +++ b/include/FramelessHelper/Widgets/FramelessDialog @@ -0,0 +1 @@ +#include diff --git a/include/FramelessHelper/Widgets/framelessdialog.h b/include/FramelessHelper/Widgets/framelessdialog.h new file mode 100644 index 0000000..7cf138c --- /dev/null +++ b/include/FramelessHelper/Widgets/framelessdialog.h @@ -0,0 +1,53 @@ +/* + * MIT License + * + * Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "framelesshelperwidgets_global.h" +#include +#include + +FRAMELESSHELPER_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcFramelessDialog) + +class FramelessDialogPrivate; + +class FRAMELESSHELPER_WIDGETS_API FramelessDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(FramelessDialog) + Q_DISABLE_COPY_MOVE(FramelessDialog) + +public: + explicit FramelessDialog(QWidget *parent = nullptr); + ~FramelessDialog() override; + +private: + QScopedPointer d_ptr; +}; + +FRAMELESSHELPER_END_NAMESPACE + +Q_DECLARE_METATYPE2(FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessDialog)) diff --git a/include/FramelessHelper/Widgets/private/framelessdialog_p.h b/include/FramelessHelper/Widgets/private/framelessdialog_p.h new file mode 100644 index 0000000..0137c33 --- /dev/null +++ b/include/FramelessHelper/Widgets/private/framelessdialog_p.h @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "framelesshelperwidgets_global.h" +#include "framelessdialog.h" +#include +#include + +FRAMELESSHELPER_BEGIN_NAMESPACE + +class WidgetsSharedHelper; + +class FRAMELESSHELPER_WIDGETS_API FramelessDialogPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(FramelessDialog) + Q_DISABLE_COPY_MOVE(FramelessDialogPrivate) + +public: + explicit FramelessDialogPrivate(FramelessDialog *q); + ~FramelessDialogPrivate() override; + + Q_NODISCARD static FramelessDialogPrivate *get(FramelessDialog *pub); + Q_NODISCARD static const FramelessDialogPrivate *get(const FramelessDialog *pub); + + Q_NODISCARD WidgetsSharedHelper *widgetsSharedHelper() const; + +private: + void initialize(); + +private: + QPointer q_ptr = nullptr; + QScopedPointer m_helper; +}; + +FRAMELESSHELPER_END_NAMESPACE + +Q_DECLARE_METATYPE2(FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessDialogPrivate)) diff --git a/include/FramelessHelper/Widgets/private/framelesswidgetshelper_p.h b/include/FramelessHelper/Widgets/private/framelesswidgetshelper_p.h index 694b196..222d571 100644 --- a/include/FramelessHelper/Widgets/private/framelesswidgetshelper_p.h +++ b/include/FramelessHelper/Widgets/private/framelesswidgetshelper_p.h @@ -67,6 +67,9 @@ public: Q_NODISCARD bool isBlurBehindWindowEnabled() const; void setBlurBehindWindowEnabled(const bool enable, const QColor &color); + void setProperty(const QByteArray &name, const QVariant &value); + Q_NODISCARD QVariant getProperty(const QByteArray &name, const QVariant &defaultValue = {}); + private: Q_NODISCARD QRect mapWidgetGeometryToScene(const QWidget * const widget) const; Q_NODISCARD bool isInSystemButtons(const QPoint &pos, Global::SystemButtonType *button) const; diff --git a/qmake/widgets.pri b/qmake/widgets.pri index 1e4ee51..0f1e999 100644 --- a/qmake/widgets.pri +++ b/qmake/widgets.pri @@ -22,12 +22,14 @@ HEADERS += \ $$WIDGETS_PUB_INC_DIR/standardsystembutton.h \ $$WIDGETS_PUB_INC_DIR/framelesswidgetshelper.h \ $$WIDGETS_PUB_INC_DIR/standardtitlebar.h \ + $$WIDGETS_PUB_INC_DIR/framelessdialog.h \ $$WIDGETS_PRIV_INC_DIR/framelesswidgetshelper_p.h \ $$WIDGETS_PRIV_INC_DIR/standardsystembutton_p.h \ $$WIDGETS_PRIV_INC_DIR/standardtitlebar_p.h \ $$WIDGETS_PRIV_INC_DIR/framelesswidget_p.h \ $$WIDGETS_PRIV_INC_DIR/framelessmainwindow_p.h \ - $$WIDGETS_PRIV_INC_DIR/widgetssharedhelper_p.h + $$WIDGETS_PRIV_INC_DIR/widgetssharedhelper_p.h \ + $$WIDGETS_PRIV_INC_DIR/framelessdialog_p.h SOURCES += \ $$WIDGETS_SRC_DIR/framelessmainwindow.cpp \ @@ -36,4 +38,5 @@ SOURCES += \ $$WIDGETS_SRC_DIR/standardsystembutton.cpp \ $$WIDGETS_SRC_DIR/standardtitlebar.cpp \ $$WIDGETS_SRC_DIR/widgetssharedhelper.cpp \ - $$WIDGETS_SRC_DIR/framelesshelperwidgets_global.cpp + $$WIDGETS_SRC_DIR/framelesshelperwidgets_global.cpp \ + $$WIDGETS_SRC_DIR/framelessdialog.cpp diff --git a/src/core/framelesshelper_qt.cpp b/src/core/framelesshelper_qt.cpp index 46f8f69..9bd1d8a 100644 --- a/src/core/framelesshelper_qt.cpp +++ b/src/core/framelesshelper_qt.cpp @@ -41,6 +41,8 @@ Q_LOGGING_CATEGORY(lcFramelessHelperQt, "wangwenx190.framelesshelper.core.impl.q using namespace Global; +FRAMELESSHELPER_BYTEARRAY_CONSTANT2(DontOverrideCursor, "FRAMELESSHELPER_DONT_OVERRIDE_CURSOR") + struct QtHelperData { SystemParameters params = {}; @@ -151,6 +153,7 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event) const bool windowFixedSize = data.params.isWindowFixedSize(); const bool ignoreThisEvent = data.params.shouldIgnoreMouseEvents(scenePos); const bool insideTitleBar = data.params.isInsideTitleBarDraggableArea(scenePos); + const bool dontOverrideCursor = data.params.getProperty(kDontOverrideCursor, false).toBool(); switch (type) { case QEvent::MouseButtonPress: { if (button == Qt::LeftButton) { @@ -188,16 +191,16 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event) } } break; case QEvent::MouseMove: { - if (!windowFixedSize) { + if (!windowFixedSize && !dontOverrideCursor) { const Qt::CursorShape cs = Utils::calculateCursorShape(window, scenePos); if (cs == Qt::ArrowCursor) { if (data.cursorShapeChanged) { - window->unsetCursor(); + data.params.unsetCursor(); const QMutexLocker locker(&g_qtHelper()->mutex); g_qtHelper()->data[windowId].cursorShapeChanged = false; } } else { - window->setCursor(cs); + data.params.setCursor(cs); const QMutexLocker locker(&g_qtHelper()->mutex); g_qtHelper()->data[windowId].cursorShapeChanged = true; } diff --git a/src/core/framelesshelper_win.cpp b/src/core/framelesshelper_win.cpp index 5c38b90..23c7032 100644 --- a/src/core/framelesshelper_win.cpp +++ b/src/core/framelesshelper_win.cpp @@ -73,6 +73,7 @@ FRAMELESSHELPER_STRING_CONSTANT(SetWindowPos) FRAMELESSHELPER_STRING_CONSTANT(TrackMouseEvent) FRAMELESSHELPER_STRING_CONSTANT(FindWindowW) FRAMELESSHELPER_STRING_CONSTANT(UnregisterClassW) +FRAMELESSHELPER_BYTEARRAY_CONSTANT2(DontOverrideCursor, "FRAMELESSHELPER_DONT_OVERRIDE_CURSOR") struct Win32HelperData { @@ -652,35 +653,46 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me // preserve the four window borders. // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains - // the proposed window rectangle for our window. During our + // the proposed window rectangle for our window. During our // processing of the `WM_NCCALCSIZE` message, we are expected to // modify the `RECT` that `lParam` points to, so that its value upon - // our return is the new client area. We must return 0 if `wParam` + // our return is the new client area. We must return 0 if `wParam` // is `FALSE`. - // // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS` - // struct. This struct contains an array of 3 `RECT`s, the first of + // struct. This struct contains an array of 3 `RECT`s, the first of // which has the exact same meaning as the `RECT` that is pointed to - // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in + // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in // conjunction with our return value, can // be used to specify portions of the source and destination window - // rectangles that are valid and should be preserved. We opt not to + // rectangles that are valid and should be preserved. We opt not to // implement an elaborate client-area preservation technique, and // simply return 0, which means "preserve the entire old client area // and align it with the upper-left corner of our new client area". - const auto clientRect = ((static_cast(wParam) == FALSE) - ? reinterpret_cast(lParam) - : &(reinterpret_cast(lParam))->rgrc[0]); + const auto clientRect = ((static_cast(wParam) == FALSE) ? + reinterpret_cast(lParam) : &(reinterpret_cast(lParam))->rgrc[0]); if (frameBorderVisible) { - // Store the original top before the default window proc applies the default frame. + // Store the original top margin before the default window procedure applies the default frame. const LONG originalTop = clientRect->top; - // Apply the default frame. + // Apply the default frame because we don't want to remove the whole window frame, + // we still need the standard window frame (the resizable frame border and the frame + // shadow) for the left, bottom and right edges. + // If we return 0 here directly, the whole window frame will be removed (which means + // there will be no resizable frame border and the frame shadow will also disappear), + // and that's also how most applications customize their title bars on Windows. It's + // totally OK but since we want to preserve as much original frame as possible, we + // can't use that solution. const LRESULT ret = DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam); if (ret != 0) { *result = ret; return true; } - // Re-apply the original top from before the size of the default frame was applied. + // Re-apply the original top from before the size of the default frame was applied, + // and the whole top frame (the title bar and the top border) is gone now. + // For the top frame, we only has 2 choices: (1) remove the top frame entirely, or + // (2) don't touch it at all. We can't preserve the top border by adjusting the top + // margin here. If we try to modify the top margin, the original title bar will + // always be painted by DWM regardless what margin we set, so here we can only remove + // the top frame entirely and use some special technique to bring the top border back. clientRect->top = originalTop; } const bool max = IsMaximized(hWnd); @@ -915,12 +927,13 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me (GetAsyncKeyState(VK_RBUTTON) < 0) : (GetAsyncKeyState(VK_LBUTTON) < 0)); const bool isTitleBar = (data.params.isInsideTitleBarDraggableArea(qtScenePos) && leftButtonPressed); const bool isFixedSize = data.params.isWindowFixedSize(); + const bool dontOverrideCursor = data.params.getProperty(kDontOverrideCursor, false).toBool(); if (frameBorderVisible) { // This will handle the left, right and bottom parts of the frame // because we didn't change them. const LRESULT originalRet = DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam); if (originalRet != HTCLIENT) { - *result = originalRet; + *result = (dontOverrideCursor ? HTBORDER : originalRet); return true; } if (full) { @@ -937,7 +950,10 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me // the little border at the top which the user can use to move or // resize the window. if (isTop && !isFixedSize) { - *result = HTTOP; + // Return HTCLIENT instead of HTBORDER here, because the mouse is + // inside our homemade title bar now, return HTCLIENT to let our + // title bar can still capture mouse events. + *result = (dontOverrideCursor ? HTCLIENT : HTTOP); return true; } if (isTitleBar) { @@ -970,6 +986,13 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me const int scaledFrameSizeX = qRound(qreal(frameSizeX) * scaleFactor); const bool isLeft = (nativeLocalPos.x < scaledFrameSizeX); const bool isRight = (nativeLocalPos.x >= (width - scaledFrameSizeX)); + if (dontOverrideCursor && (isTop || isBottom || isLeft || isRight)) { + // Return HTCLIENT instead of HTBORDER here, because the mouse is + // inside the window now, return HTCLIENT to let the controls + // inside our window can still capture mouse events. + *result = HTCLIENT; + return true; + } if (isTop) { if (isLeft) { *result = HTTOPLEFT; diff --git a/src/core/micamaterial.cpp b/src/core/micamaterial.cpp index 08e632b..4225ed5 100644 --- a/src/core/micamaterial.cpp +++ b/src/core/micamaterial.cpp @@ -384,9 +384,9 @@ static inline void expblur(QImage &img, qreal radius, const bool improvedQuality if (p) { p->save(); + p->setRenderHints(QPainter::Antialiasing | + QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); p->scale(scale, scale); - p->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing - | QPainter::SmoothPixmapTransform, quality); #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)) const QSize imageSize = blurImage.deviceIndependentSize().toSize(); #else @@ -527,15 +527,20 @@ void MicaMaterialPrivate::maybeGenerateBlurredWallpaper(const bool force) const QRect desktopRect = {desktopOriginPoint, size}; if (aspectStyle == WallpaperAspectStyle::Tile) { QPainter bufferPainter(&buffer); - const QBrush brush(image); - bufferPainter.fillRect(desktopRect, brush); + bufferPainter.setRenderHints(QPainter::Antialiasing | + QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); + bufferPainter.fillRect(desktopRect, QBrush(image)); } else { QPainter bufferPainter(&buffer); + bufferPainter.setRenderHints(QPainter::Antialiasing | + QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); const QRect rect = alignedRect(Qt::LeftToRight, Qt::AlignCenter, image.size(), desktopRect); bufferPainter.drawImage(rect.topLeft(), image); } g_micaMaterialData()->mutex.lock(); QPainter painter(&g_micaMaterialData()->blurredWallpaper); + painter.setRenderHints(QPainter::Antialiasing | + QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); #if 1 qt_blurImage(&painter, buffer, kDefaultBlurRadius, true, false); #else @@ -555,6 +560,8 @@ void MicaMaterialPrivate::updateMaterialBrush() fillColor.setAlphaF(0.9f); micaTexture.fill(fillColor); QPainter painter(&micaTexture); + painter.setRenderHints(QPainter::Antialiasing | + QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); painter.setOpacity(tintOpacity); const QRect rect = {QPoint(0, 0), micaTexture.size()}; painter.fillRect(rect, tintColor); @@ -573,6 +580,8 @@ void MicaMaterialPrivate::paint(QPainter *painter, const QSize &size, const QPoi } static constexpr const QPoint originPoint = {0, 0}; painter->save(); + painter->setRenderHints(QPainter::Antialiasing | + QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); g_micaMaterialData()->mutex.lock(); painter->drawPixmap(originPoint, g_micaMaterialData()->blurredWallpaper, QRect(pos, size)); g_micaMaterialData()->mutex.unlock(); diff --git a/src/core/utils_win.cpp b/src/core/utils_win.cpp index 5e7c033..c646622 100644 --- a/src/core/utils_win.cpp +++ b/src/core/utils_win.cpp @@ -761,7 +761,7 @@ void Utils::showSystemMenu(const WId windowId, const QPoint &pos, const bool sel // Tweak the menu items according to the current window status. const bool maxOrFull = (IsMaximized(hWnd) || isFullScreen(windowId)); const bool fixedSize = isWindowFixedSize(); - EnableMenuItem(hMenu, SC_RESTORE, (MF_BYCOMMAND | ((maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED))); + EnableMenuItem(hMenu, SC_RESTORE, (MF_BYCOMMAND | ((maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_GRAYED))); // The first menu item should be selected by default if the menu is brought // up by keyboard. I don't know how to pre-select a menu item but it seems // highlight can do the job. However, there's an annoying issue if we do @@ -772,10 +772,10 @@ void Utils::showSystemMenu(const WId windowId, const QPoint &pos, const bool sel // highlight bar to indicate the current selected menu item, which will make // the menu look kind of weird. Currently I don't know how to fix this issue. HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | (selectFirstEntry ? MFS_HILITE : MFS_UNHILITE))); - EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | (!maxOrFull ? MFS_ENABLED : MFS_DISABLED))); - EnableMenuItem(hMenu, SC_SIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED))); + EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | (!maxOrFull ? MFS_ENABLED : MFS_GRAYED))); + EnableMenuItem(hMenu, SC_SIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_GRAYED))); EnableMenuItem(hMenu, SC_MINIMIZE, (MF_BYCOMMAND | MFS_ENABLED)); - EnableMenuItem(hMenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED))); + EnableMenuItem(hMenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_GRAYED))); EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED)); // The default menu item will appear in bold font. There can only be one default diff --git a/src/quick/framelessquickhelper.cpp b/src/quick/framelessquickhelper.cpp index 606d133..6271825 100644 --- a/src/quick/framelessquickhelper.cpp +++ b/src/quick/framelessquickhelper.cpp @@ -196,6 +196,10 @@ void FramelessQuickHelperPrivate::attachToWindow() params.shouldIgnoreMouseEvents = [this](const QPoint &pos) -> bool { return shouldIgnoreMouseEvents(pos); }; params.showSystemMenu = [this](const QPoint &pos) -> void { showSystemMenu(pos); }; params.getCurrentApplicationType = []() -> ApplicationType { return ApplicationType::Quick; }; + params.setProperty = [this](const QByteArray &name, const QVariant &value) -> void { setProperty(name, value); }; + params.getProperty = [this](const QByteArray &name, const QVariant &defaultValue) -> QVariant { return getProperty(name, defaultValue); }; + params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); }; + params.unsetCursor = [window]() -> void { window->unsetCursor(); }; g_quickHelper()->mutex.lock(); data->params = params; @@ -454,6 +458,36 @@ void FramelessQuickHelperPrivate::setBlurBehindWindowEnabled(const bool value, c } } +void FramelessQuickHelperPrivate::setProperty(const QByteArray &name, const QVariant &value) +{ + Q_ASSERT(!name.isEmpty()); + Q_ASSERT(value.isValid()); + if (name.isEmpty() || !value.isValid()) { + return; + } + Q_Q(FramelessQuickHelper); + QQuickWindow * const window = q->window(); + if (!window) { + return; + } + window->setProperty(name.constData(), value); +} + +QVariant FramelessQuickHelperPrivate::getProperty(const QByteArray &name, const QVariant &defaultValue) +{ + Q_ASSERT(!name.isEmpty()); + if (name.isEmpty()) { + return {}; + } + Q_Q(FramelessQuickHelper); + const QQuickWindow * const window = q->window(); + if (!window) { + return {}; + } + const QVariant value = window->property(name.constData()); + return (value.isValid() ? value : defaultValue); +} + bool FramelessQuickHelperPrivate::eventFilter(QObject *object, QEvent *event) { Q_ASSERT(object); diff --git a/src/quick/quickimageitem.cpp b/src/quick/quickimageitem.cpp index 61a76fb..dbd187b 100644 --- a/src/quick/quickimageitem.cpp +++ b/src/quick/quickimageitem.cpp @@ -85,8 +85,8 @@ void QuickImageItemPrivate::paint(QPainter *painter) const return; } painter->save(); - painter->setRenderHints(QPainter::Antialiasing - | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); + painter->setRenderHints(QPainter::Antialiasing | + QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); switch (m_source.userType()) { case QMetaType::QUrl: fromUrl(m_source.toUrl(), painter); diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 87bbb4f..a14a3bb 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -35,6 +35,7 @@ set(PUBLIC_HEADERS ${INCLUDE_PREFIX}/standardsystembutton.h ${INCLUDE_PREFIX}/framelesswidgetshelper.h ${INCLUDE_PREFIX}/standardtitlebar.h + ${INCLUDE_PREFIX}/framelessdialog.h ) set(PUBLIC_HEADERS_ALIAS @@ -44,6 +45,7 @@ set(PUBLIC_HEADERS_ALIAS ${INCLUDE_PREFIX}/StandardSystemButton ${INCLUDE_PREFIX}/FramelessWidgetsHelper ${INCLUDE_PREFIX}/StandardTitleBar + ${INCLUDE_PREFIX}/FramelessDialog ) set(PRIVATE_HEADERS @@ -53,6 +55,7 @@ set(PRIVATE_HEADERS ${INCLUDE_PREFIX}/private/framelesswidget_p.h ${INCLUDE_PREFIX}/private/framelessmainwindow_p.h ${INCLUDE_PREFIX}/private/widgetssharedhelper_p.h + ${INCLUDE_PREFIX}/private/framelessdialog_p.h ) set(SOURCES @@ -63,6 +66,7 @@ set(SOURCES standardtitlebar.cpp widgetssharedhelper.cpp framelesshelperwidgets_global.cpp + framelessdialog.cpp ) if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC) diff --git a/src/widgets/framelessdialog.cpp b/src/widgets/framelessdialog.cpp new file mode 100644 index 0000000..6966772 --- /dev/null +++ b/src/widgets/framelessdialog.cpp @@ -0,0 +1,91 @@ +/* + * MIT License + * + * Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "framelessdialog.h" +#include "framelessdialog_p.h" +#include "framelesswidgetshelper.h" +#include "widgetssharedhelper_p.h" +#include + +FRAMELESSHELPER_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcFramelessDialog, "wangwenx190.framelesshelper.widgets.framelessdialog") +#define INFO qCInfo(lcFramelessDialog) +#define DEBUG qCDebug(lcFramelessDialog) +#define WARNING qCWarning(lcFramelessDialog) +#define CRITICAL qCCritical(lcFramelessDialog) + +using namespace Global; + +FramelessDialogPrivate::FramelessDialogPrivate(FramelessDialog *q) : QObject(q) +{ + Q_ASSERT(q); + if (!q) { + return; + } + q_ptr = q; + initialize(); +} + +FramelessDialogPrivate::~FramelessDialogPrivate() = default; + +FramelessDialogPrivate *FramelessDialogPrivate::get(FramelessDialog *pub) +{ + Q_ASSERT(pub); + if (!pub) { + return nullptr; + } + return pub->d_func(); +} + +const FramelessDialogPrivate *FramelessDialogPrivate::get(const FramelessDialog *pub) +{ + Q_ASSERT(pub); + if (!pub) { + return nullptr; + } + return pub->d_func(); +} + +void FramelessDialogPrivate::initialize() +{ + Q_Q(FramelessDialog); + FramelessWidgetsHelper::get(q)->extendsContentIntoTitleBar(); + m_helper.reset(new WidgetsSharedHelper(this)); + m_helper->setup(q); +} + +WidgetsSharedHelper *FramelessDialogPrivate::widgetsSharedHelper() const +{ + return (m_helper.isNull() ? nullptr : m_helper.data()); +} + +FramelessDialog::FramelessDialog(QWidget *parent) + : QDialog(parent), d_ptr(new FramelessDialogPrivate(this)) +{ +} + +FramelessDialog::~FramelessDialog() = default; + +FRAMELESSHELPER_END_NAMESPACE diff --git a/src/widgets/framelessdialog.h b/src/widgets/framelessdialog.h new file mode 100644 index 0000000..4b5e2a8 --- /dev/null +++ b/src/widgets/framelessdialog.h @@ -0,0 +1 @@ +#include "../../include/FramelessHelper/Widgets/framelessdialog.h" diff --git a/src/widgets/framelessdialog_p.h b/src/widgets/framelessdialog_p.h new file mode 100644 index 0000000..f05c95e --- /dev/null +++ b/src/widgets/framelessdialog_p.h @@ -0,0 +1 @@ +#include "../../include/FramelessHelper/Widgets/private/framelessdialog_p.h" diff --git a/src/widgets/framelesswidgetshelper.cpp b/src/widgets/framelesswidgetshelper.cpp index 3f7d27c..5b5572b 100644 --- a/src/widgets/framelesswidgetshelper.cpp +++ b/src/widgets/framelesswidgetshelper.cpp @@ -28,6 +28,8 @@ #include "framelesswidget_p.h" #include "framelessmainwindow.h" #include "framelessmainwindow_p.h" +#include "framelessdialog.h" +#include "framelessdialog_p.h" #include "widgetssharedhelper_p.h" #include #include @@ -87,6 +89,11 @@ Q_GLOBAL_STATIC(WidgetsHelper, g_widgetsHelper) return mainWindowPriv->widgetsSharedHelper(); } } + if (const auto dialog = qobject_cast(window)) { + if (const auto dialogPriv = FramelessDialogPrivate::get(dialog)) { + return dialogPriv->widgetsSharedHelper(); + } + } return nullptr; } @@ -230,6 +237,36 @@ void FramelessWidgetsHelperPrivate::setBlurBehindWindowEnabled(const bool enable } } +void FramelessWidgetsHelperPrivate::setProperty(const QByteArray &name, const QVariant &value) +{ + Q_ASSERT(!name.isEmpty()); + Q_ASSERT(value.isValid()); + if (name.isEmpty() || !value.isValid()) { + return; + } + QWidget * const window = getWindow(); + Q_ASSERT(window); + if (!window) { + return; + } + window->setProperty(name.constData(), value); +} + +QVariant FramelessWidgetsHelperPrivate::getProperty(const QByteArray &name, const QVariant &defaultValue) +{ + Q_ASSERT(!name.isEmpty()); + if (name.isEmpty()) { + return {}; + } + const QWidget * const window = getWindow(); + Q_ASSERT(window); + if (!window) { + return {}; + } + const QVariant value = window->property(name.constData()); + return (value.isValid() ? value : defaultValue); +} + void FramelessWidgetsHelperPrivate::setTitleBarWidget(QWidget *widget) { Q_ASSERT(widget); @@ -332,6 +369,10 @@ void FramelessWidgetsHelperPrivate::attachToWindow() params.shouldIgnoreMouseEvents = [this](const QPoint &pos) -> bool { return shouldIgnoreMouseEvents(pos); }; params.showSystemMenu = [this](const QPoint &pos) -> void { showSystemMenu(pos); }; params.getCurrentApplicationType = []() -> ApplicationType { return ApplicationType::Widgets; }; + params.setProperty = [this](const QByteArray &name, const QVariant &value) -> void { setProperty(name, value); }; + params.getProperty = [this](const QByteArray &name, const QVariant &defaultValue) -> QVariant { return getProperty(name, defaultValue); }; + params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); }; + params.unsetCursor = [window]() -> void { window->unsetCursor(); }; g_widgetsHelper()->mutex.lock(); data->params = params; diff --git a/src/widgets/widgetssharedhelper.cpp b/src/widgets/widgetssharedhelper.cpp index ea610e4..46d3eb5 100644 --- a/src/widgets/widgetssharedhelper.cpp +++ b/src/widgets/widgetssharedhelper.cpp @@ -146,9 +146,17 @@ void WidgetsSharedHelper::changeEventHandler(QEvent *event) return; } updateContentsMargins(); - QMetaObject::invokeMethod(m_targetWidget, "hiddenChanged"); - QMetaObject::invokeMethod(m_targetWidget, "normalChanged"); - QMetaObject::invokeMethod(m_targetWidget, "zoomedChanged"); + if (const auto mo = m_targetWidget->metaObject()) { + if (const int idx = mo->indexOfSignal(QMetaObject::normalizedSignature("hiddenChanged()").constData()); idx >= 0) { + QMetaObject::invokeMethod(m_targetWidget, "hiddenChanged"); + } + if (const int idx = mo->indexOfSignal(QMetaObject::normalizedSignature("normalChanged()").constData()); idx >= 0) { + QMetaObject::invokeMethod(m_targetWidget, "normalChanged"); + } + if (const int idx = mo->indexOfSignal(QMetaObject::normalizedSignature("zoomedChanged()").constData()); idx >= 0) { + QMetaObject::invokeMethod(m_targetWidget, "zoomedChanged"); + } + } #ifdef Q_OS_WINDOWS const WId windowId = m_targetWidget->winId(); static const bool isWin11OrGreater = Utils::isWindowsVersionOrGreater(WindowsVersion::_11_21H2); @@ -181,6 +189,10 @@ void WidgetsSharedHelper::paintEventHandler(QPaintEvent *event) if (shouldDrawFrameBorder()) { QPainter painter(m_targetWidget); painter.save(); + painter.setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); + // We can't enable antialiasing here, because the border is only 1px height, + // it's too thin and antialiasing will break it's painting. + painter.setRenderHint(QPainter::Antialiasing, false); QPen pen = {}; pen.setColor(Utils::getFrameBorderColor(m_targetWidget->isActiveWindow())); pen.setWidth(kDefaultWindowFrameBorderThickness);