diff --git a/examples/QWidget2/QWidget2.pro b/examples/QWidget2/QWidget2.pro new file mode 100644 index 0000000..29d1b75 --- /dev/null +++ b/examples/QWidget2/QWidget2.pro @@ -0,0 +1,6 @@ +TARGET = QWidget2 +TEMPLATE = app +QT += widgets +HEADERS += widget.h +SOURCES += widget.cpp main.cpp +include($$PWD/../common.pri) diff --git a/examples/QWidget2/main.cpp b/examples/QWidget2/main.cpp new file mode 100644 index 0000000..4c704e1 --- /dev/null +++ b/examples/QWidget2/main.cpp @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (C) 2020 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 "widget.h" +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + // High DPI scaling is enabled by default from Qt 6 +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + // Windows: we are using the manifest file to get maximum compatibility + // because some APIs are not supprted on old systems such as Windows 7 + // and Windows 8. And once we have set the DPI awareness level in the + // manifest file, any attemptation to try to change it through API will + // fail. In other words, Qt won't be able to enable or disable high DPI + // scaling or change the DPI awareness level once we have set it in the + // manifest file. So the following two lines are uesless actually (However, + // they are still useful on other platforms). + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + // Don't round the scale factor. + // This will break QWidget applications because they can't render correctly. + // Qt Quick applications won't have this issue. + QGuiApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif +#endif + + QApplication application(argc, argv); + Widget widget; + QStyleOption option; + option.initFrom(&widget); + const QIcon icon = widget.style()->standardIcon(QStyle::SP_ComputerIcon, &option); + widget.setWindowIcon(icon); + widget.setWindowTitle(QObject::tr("Hello, World!")); + widget.resize(800, 600); + widget.show(); + return QApplication::exec(); +} diff --git a/examples/QWidget2/widget.cpp b/examples/QWidget2/widget.cpp new file mode 100644 index 0000000..725bcc5 --- /dev/null +++ b/examples/QWidget2/widget.cpp @@ -0,0 +1,259 @@ +/* + * MIT License + * + * Copyright (C) 2020 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 "widget.h" +#include "../../framelesshelper.h" +#include +#include +#include +#include +#include +#include +#include +#include + +Q_GLOBAL_STATIC(FramelessHelper, framelessHelper) + +ContentsWidget::ContentsWidget(QWidget *parent) : QWidget(parent) +{ + setAttribute(Qt::WA_StyledBackground); +} + +void ContentsWidget::setShouldDrawWindowBorder(const bool val) +{ + m_bShouldDrawWindowBorder = val; + update(); +} + +bool ContentsWidget::getShouldDrawWindowBorder() const +{ + return m_bShouldDrawWindowBorder; +} + +void ContentsWidget::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + if (m_bShouldDrawWindowBorder) { + QPainter painter(this); + painter.save(); + painter.setPen({Qt::black, 1.5}); + painter.drawLine(0, 0, width(), 0); + painter.drawLine(0, height(), width(), height()); + painter.drawLine(0, 0, 0, height()); + painter.drawLine(width(), 0, width(), height()); + painter.restore(); + } +} + +Widget::Widget(QWidget *parent) : QWidget(parent) +{ + titleBarHeight = framelessHelper()->getTitleBarHeight(); + createWinId(); + setupUi(); + initBackgroundWindow(); + installEventFilter(this); +} + +bool Widget::isNormal() const +{ + return (!isMinimized() && !isMaximized() && !isFullScreen()); +} + +void Widget::setupUi() +{ + contentsWidget = new ContentsWidget(this); + contentsWidget->setObjectName(QLatin1String("contentsWidget")); + contentsWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + const QSize systemButtonSize = {qRound(titleBarHeight * 1.5), titleBarHeight}; + const QSize systemIconSize = {17, 17}; + titleBarWidget = new QWidget(contentsWidget); + titleBarWidget->setObjectName(QLatin1String("titleBarWidget")); + titleBarWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + titleBarWidget->setFixedHeight(titleBarHeight); + windowIconButton = new QPushButton(this); + windowIconButton->setObjectName(QLatin1String("windowIconButton")); + windowIconButton->setFixedSize(systemIconSize); + windowIconButton->setIconSize(systemIconSize); + connect(this, &Widget::windowIconChanged, windowIconButton, &QPushButton::setIcon); + windowTitleLabel = new QLabel(this); + QFont f = font(); + f.setPointSizeF(8.63); + windowTitleLabel->setFont(f); + connect(this, &Widget::windowTitleChanged, windowTitleLabel, &QLabel::setText); + minimizeButton = new QPushButton(this); + minimizeButton->setObjectName(QLatin1String("minimizeButton")); + minimizeButton->setFixedSize(systemButtonSize); + minimizeButton->setIconSize(systemButtonSize); + minimizeButton->setIcon(QIcon(QLatin1String(":/images/button_minimize_black.svg"))); + connect(minimizeButton, &QPushButton::clicked, this, &Widget::showMinimized); + maximizeButton = new QPushButton(this); + maximizeButton->setObjectName(QLatin1String("maximizeButton")); + maximizeButton->setFixedSize(systemButtonSize); + maximizeButton->setIconSize(systemButtonSize); + maximizeButton->setIcon(QIcon(QLatin1String(":/images/button_maximize_black.svg"))); + connect(maximizeButton, &QPushButton::clicked, this, [this]() { + if (isMaximized()) { + showNormal(); + } else { + showMaximized(); + } + }); + closeButton = new QPushButton(this); + closeButton->setObjectName(QLatin1String("closeButton")); + closeButton->setFixedSize(systemButtonSize); + closeButton->setIconSize(systemButtonSize); + closeButton->setIcon(QIcon(QLatin1String(":/images/button_close_black.svg"))); + connect(closeButton, &QPushButton::clicked, this, &Widget::close); + const auto titleBarWidgetLayout = new QHBoxLayout(titleBarWidget); + titleBarWidgetLayout->setSpacing(0); + titleBarWidgetLayout->setContentsMargins(0, 0, 0, 0); + titleBarWidgetLayout->addSpacerItem( + new QSpacerItem(7, 20, QSizePolicy::Fixed, QSizePolicy::Fixed)); + titleBarWidgetLayout->addWidget(windowIconButton); + titleBarWidgetLayout->addSpacerItem( + new QSpacerItem(3, 20, QSizePolicy::Fixed, QSizePolicy::Fixed)); + titleBarWidgetLayout->addWidget(windowTitleLabel); + titleBarWidgetLayout->addStretch(); + titleBarWidgetLayout->addWidget(minimizeButton); + titleBarWidgetLayout->addWidget(maximizeButton); + titleBarWidgetLayout->addWidget(closeButton); + titleBarWidget->setLayout(titleBarWidgetLayout); + const auto contentsWidgetLayout = new QVBoxLayout(contentsWidget); + contentsWidgetLayout->setSpacing(0); + contentsWidgetLayout->setContentsMargins(1, 1, 1, 0); + contentsWidgetLayout->addWidget(titleBarWidget); + contentsWidgetLayout->addStretch(); + contentsWidget->setLayout(contentsWidgetLayout); + const auto backgroundWindowLayout = new QVBoxLayout(this); + backgroundWindowLayout->setSpacing(0); + backgroundWindowLayout->addWidget(contentsWidget); + setLayout(backgroundWindowLayout); + setStyleSheet(QLatin1String(R"( +#contentsWidget { + background-color: #f0f0f0; +} + +#titleBarWidget { + background-color: white; +} + +#windowIconButton, #minimizeButton, #maximizeButton, #closeButton { + border-style: none; + background-color: transparent; +} + +#minimizeButton:hover, #maximizeButton:hover { + background-color: #80c7c7c7; +} + +#minimizeButton:pressed, #maximizeButton:pressed { + background-color: #80808080; +} + +#closeButton:hover { + background-color: #e81123; +} + +#closeButton:pressed { + background-color: #8c0a15; +} +)")); +} + +void Widget::initBackgroundWindow() +{ + QWindow *win = windowHandle(); + framelessHelper()->removeWindowFrame(win); + framelessHelper()->addIgnoreObject(win, windowIconButton); + framelessHelper()->addIgnoreObject(win, minimizeButton); + framelessHelper()->addIgnoreObject(win, maximizeButton); + framelessHelper()->addIgnoreObject(win, closeButton); + framelessHelper()->setTitleBarHeight(titleBarHeight + framelessHelper()->getBorderHeight()); + //setAttribute(Qt::WA_Hover); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + shadowEffect = new QGraphicsDropShadowEffect(this); + shadowEffect->setOffset(0, 0); + contentsWidget->setGraphicsEffect(shadowEffect); + setFrameShadowEnabled(); + setFrameShadowActive(); +} + +void Widget::setFrameShadowEnabled(const bool enable) +{ + if (enable) { + const int bw = framelessHelper()->getBorderWidth(); + const int bh = framelessHelper()->getBorderHeight(); + layout()->setContentsMargins(bw, bh, bw, bh); + shadowEffect->setEnabled(true); + } else { + shadowEffect->setEnabled(false); + layout()->setContentsMargins(0, 0, 0, 0); + } +} + +void Widget::setFrameShadowActive(const bool active) +{ + if (active) { + shadowEffect->setColor({0, 0, 0, 80}); + shadowEffect->setBlurRadius(25); + } else { + shadowEffect->setColor({0, 0, 0, 60}); + shadowEffect->setBlurRadius(20); + } +} + +bool Widget::eventFilter(QObject *object, QEvent *event) +{ + Q_ASSERT(object); + Q_ASSERT(event); + if (object == this) { + switch (event->type()) { + case QEvent::WindowStateChange: { + const bool normal = isNormal(); + contentsWidget->setShouldDrawWindowBorder(normal); + setFrameShadowEnabled(normal); + framelessHelper()->setTitleBarHeight( + titleBarHeight + (normal ? framelessHelper()->getBorderHeight() : 0)); + const QLatin1String maxIconPath = QLatin1String{":/images/button_maximize_black.svg"}; + const QLatin1String restoreIconPath = QLatin1String{ + ":/images/button_restore_black.svg"}; + maximizeButton->setIcon(QIcon(normal ? maxIconPath : restoreIconPath)); + } break; + case QEvent::WindowActivate: { + if (isNormal()) { + setFrameShadowActive(); + } + } break; + case QEvent::WindowDeactivate: { + if (isNormal()) { + setFrameShadowActive(false); + } + } break; + default: + break; + } + } + return QWidget::eventFilter(object, event); +} diff --git a/examples/QWidget2/widget.h b/examples/QWidget2/widget.h new file mode 100644 index 0000000..7412e46 --- /dev/null +++ b/examples/QWidget2/widget.h @@ -0,0 +1,82 @@ +/* + * MIT License + * + * Copyright (C) 2020 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 + +class QGraphicsDropShadowEffect; +class QPushButton; +class QLabel; + +class ContentsWidget : public QWidget +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ContentsWidget) + +public: + explicit ContentsWidget(QWidget *parent = nullptr); + ~ContentsWidget() override = default; + + void setShouldDrawWindowBorder(const bool val); + bool getShouldDrawWindowBorder() const; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + bool m_bShouldDrawWindowBorder = true; +}; + +class Widget : public QWidget +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(Widget) + +public: + explicit Widget(QWidget *parent = nullptr); + ~Widget() override = default; + + bool isNormal() const; + +protected: + bool eventFilter(QObject *object, QEvent *event) override; + +private: + void setupUi(); + void initBackgroundWindow(); + void setFrameShadowEnabled(const bool enable = true); + void setFrameShadowActive(const bool active = true); + +private: + int titleBarHeight = 30; + ContentsWidget *contentsWidget = nullptr; + QGraphicsDropShadowEffect *shadowEffect = nullptr; + QWidget *titleBarWidget = nullptr; + QPushButton *windowIconButton = nullptr; + QLabel *windowTitleLabel = nullptr; + QPushButton *minimizeButton = nullptr; + QPushButton *maximizeButton = nullptr; + QPushButton *closeButton = nullptr; +}; diff --git a/examples/Win32Demo/widget.cpp b/examples/Win32Demo/widget.cpp index be4e493..cdd52ec 100644 --- a/examples/Win32Demo/widget.cpp +++ b/examples/Win32Demo/widget.cpp @@ -175,12 +175,12 @@ void Widget::setupUi() horizontalLayout->setSpacing(0); horizontalLayout->setContentsMargins(0, 0, 0, 0); horizontalSpacer_7 = new QSpacerItem(3, 20, QSizePolicy::Fixed, QSizePolicy::Minimum); - horizontalLayout->addItem(horizontalSpacer_7); + horizontalLayout->addSpacerItem(horizontalSpacer_7); iconButton = new QPushButton(titleBarWidget); iconButton->setObjectName(QLatin1String("iconButton")); horizontalLayout->addWidget(iconButton); horizontalSpacer = new QSpacerItem(3, 20, QSizePolicy::Fixed, QSizePolicy::Minimum); - horizontalLayout->addItem(horizontalSpacer); + horizontalLayout->addSpacerItem(horizontalSpacer); titleLabel = new QLabel(titleBarWidget); titleLabel->setObjectName(QLatin1String("titleLabel")); QFont font; @@ -188,7 +188,7 @@ void Widget::setupUi() titleLabel->setFont(font); horizontalLayout->addWidget(titleLabel); horizontalSpacer_2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout->addItem(horizontalSpacer_2); + horizontalLayout->addSpacerItem(horizontalSpacer_2); minimizeButton = new QPushButton(titleBarWidget); minimizeButton->setObjectName(QLatin1String("minimizeButton")); QSizePolicy sizePolicy1(QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -235,10 +235,10 @@ void Widget::setupUi() contentsWidget->setSizePolicy(sizePolicy2); verticalLayout_2 = new QVBoxLayout(contentsWidget); verticalSpacer_2 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); - verticalLayout_2->addItem(verticalSpacer_2); + verticalLayout_2->addSpacerItem(verticalSpacer_2); horizontalLayout_2 = new QHBoxLayout(); horizontalSpacer_3 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer_3); + horizontalLayout_2->addSpacerItem(horizontalSpacer_3); controlPanelWidget = new QWidget(contentsWidget); QSizePolicy sizePolicy3(QSizePolicy::Maximum, QSizePolicy::Maximum); sizePolicy3.setHorizontalStretch(0); @@ -275,18 +275,18 @@ void Widget::setupUi() verticalLayout->addWidget(resizableCB); horizontalLayout_2->addWidget(controlPanelWidget); horizontalSpacer_4 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer_4); + horizontalLayout_2->addSpacerItem(horizontalSpacer_4); verticalLayout_2->addLayout(horizontalLayout_2); verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); - verticalLayout_2->addItem(verticalSpacer); + verticalLayout_2->addSpacerItem(verticalSpacer); horizontalLayout_3 = new QHBoxLayout(); horizontalSpacer_5 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_3->addItem(horizontalSpacer_5); + horizontalLayout_3->addSpacerItem(horizontalSpacer_5); moveCenterButton = new QPushButton(contentsWidget); moveCenterButton->setFont(font1); horizontalLayout_3->addWidget(moveCenterButton); horizontalSpacer_6 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_3->addItem(horizontalSpacer_6); + horizontalLayout_3->addSpacerItem(horizontalSpacer_6); verticalLayout_2->addLayout(horizontalLayout_3); verticalLayout_3->addWidget(contentsWidget); if (shouldDrawBorder()) { @@ -425,7 +425,7 @@ void Widget::paintEvent(QPaintEvent *event) if (shouldDrawBorder()) { QPainter painter(this); painter.save(); - painter.setPen({borderColor(), 2.0}); + painter.setPen({borderColor(), 1.5}); painter.drawLine(0, 0, width(), 0); painter.drawLine(0, height(), width(), height()); painter.drawLine(0, 0, 0, height()); diff --git a/examples/examples.pro b/examples/examples.pro index bd278ad..8b96a4b 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -1,5 +1,5 @@ TEMPLATE = subdirs CONFIG -= ordered -qtHaveModule(widgets): SUBDIRS += QWidget QMainWindow +qtHaveModule(widgets): SUBDIRS += QWidget QWidget2 QMainWindow qtHaveModule(quick): SUBDIRS += Quick win32: qtHaveModule(widgets): SUBDIRS += Win32Demo diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 14058e4..8ea568d 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -217,7 +217,8 @@ void FramelessHelper::setTitleBarEnabled(const QWindow *window, const bool val) void FramelessHelper::removeWindowFrame(QWindow *window, const bool center) { Q_ASSERT(window); - window->setFlags(window->flags() | Qt::FramelessWindowHint); + window->setFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint + | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); // MouseTracking is always enabled for QWindow. window->installEventFilter(this); if (center) {