diff --git a/CMakeLists.txt b/CMakeLists.txt index e6344fff..e175b75b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,11 @@ option(FRAMELESSHELPER_NO_MICA_MATERIAL "Disable the cross-platform homemade Mic option(FRAMELESSHELPER_NO_BORDER_PAINTER "Disable the cross-platform window frame border painter." OFF) option(FRAMELESSHELPER_NO_SYSTEM_BUTTON "Disable the pre-defined StandardSystemButton control." OFF) +if(FRAMELESSHELPER_NO_WINDOW AND FRAMELESSHELPER_BUILD_EXAMPLES) + message(WARNING "You can't build the examples when the FramelessWindow class is disabled at the same time!") + set(FRAMELESSHELPER_BUILD_EXAMPLES OFF) +endif() + set(PROJECT_VERSION_HEX "0x00000000") math(EXPR PROJECT_VERSION_HEX "((${PROJECT_VERSION_MAJOR} & 0xff) << 24) | ((${PROJECT_VERSION_MINOR} & 0xff) << 16) | ((${PROJECT_VERSION_PATCH} & 0xff) << 8)" OUTPUT_FORMAT HEXADECIMAL) diff --git a/README.md b/README.md index 2f83d4a6..87ab32c8 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,23 @@ Cross-platform window customization framework for Qt Widgets and Qt Quick. Suppo You can join our [Discord channel](https://discord.gg/grrM4Tmesy) to communicate with us. You can share your findings, thoughts and ideas on improving / implementing FramelessHelper functionalities on more platforms and apps! -## Roadmap +## TODO - Common: Add cross-platform customizable system menu for both Qt Widgets and Qt Quick. Also supports both light and dark theme. - Examples: Add QtWebEngine based demo projects for both Qt Widgets and Qt Quick. The whole user interface will be written in HTML instead of C++/QML. - Examples: Add demo projects that emulate the classic appearance of UWP applications. They will have a backward button on the left side of the title bar and a search box in the middle of the title bar. And maybe a side bar on the left side to switch between different pages. -- Examples: Add demo projects that the main window is not resizable. - Examples: Add demo projects that have transparent background and doesn't have rectangular window frame. +- Examples: Add demo projects based on QRhiWidget and QRhiQuickItem. - Feature requests are welcome! ## Highlights v2.5 - General: The file size of FramelessHelper binaries should be smaller than before, due to most static string literals and some internal structures are constexpr now, this change may also help to improve the general performance. - General: The performance should be improved quite some bit, due to most double lookups of Qt container types and unnecessary data copies are avoided now. +- Snap Layout: The snap layout implementation has been COMPLETELY rewritten. It now behaves almost exactly the same with native windows! - Mica Material: FramelessHelper now prefers speed over quality. This change will lower the image quality but since the image is highly blurred anyway, there should not be any significant differences in the final user experience. -- Build system: Improved RPATH support. +- Build system: Improved RPATH support (UNIX systems). +- Build system: Support modular build. - Routine bug fixes and internal refactorings. ## Highlights v2.4 @@ -383,10 +385,7 @@ Please refer to the demo projects to see more detailed usages: [examples](./exam - Due to there are many sub-versions of Windows 10, it's highly recommended to use the latest version of Windows 10, at least **no older than Windows 10 1809**. If you try to use this framework on some very old Windows 10 versions such as 1507 or 1607, there may be some compatibility issues. Using this framework on Windows 7 is also supported but not recommended. To get the most stable behavior and the best appearance, you should use it on the latest version of Windows 10 or Windows 11. - To make the snap layout work as expected, there are some additional rules for your homemade system buttons to follow: - **Add a manifest file to your application. In the manifest file, you need to claim your application supports Windows 11 explicitly. This step is VERY VERY IMPORTANT. Without this step, the snap layout feature can't be enabled.** - - Call `setSystemButton()` for each button to let FramelessHelper know which is the minimize/maximize/close button. - - System buttons will not be able to receive any keyboard events so there's no need to handle these events inside these buttons. - - The mouse events of the system buttons are all emulated by FramelessHelper, they are not sent by the OS, so don't trust them too much. - - I know this is making everything complicated but unfortunately we can't avoid this mess if we need to support the snap layout feature. Snap layout is really only designed for the original standard window frame, so if we want to forcely support it without a standard window frame, many black magic will be needed. + - Call `setSystemButton()` for each button (it can be any *QWidget* or *QQuickItem*) to let FramelessHelper know which is the minimize/maximize/close button. ### Linux @@ -415,6 +414,17 @@ First of all, it's a Qt issue, not caused by FramelessHelper. And it should not Short answer: it's impossible. Full explaination: of course we can use the same technique we use on Win10 to remove the whole top part of the window and preserve the other three frame borders at the same time, but on Win10 we can bring the top border back, either by doing some black magic in the `WM_PAINT` handler or draw a thin frame border manually ourself, however, it's impossible to do this on Win7. I've tried it on Win7 already and sadly the result is the `WM_PAINT` trick won't work on Win7, and we also can't draw a frame border which looks very similar to the original one (a semi-transparent rectangle, blended with system's accent color and the visual content behind the window, also with some blur effect applied). But it seems Google Chrome/Microsoft Edge's installer have achieved what we wanted to do, how? Well, their installer is open source and I've read it's code already. They achieve that by overlapping two windows, one normal window on the bottom, another border-less window on the top to cover the bottom window's title bar. They draw their homemade title bar on the border-less window and use it to emulate the standard title bar's behavior. The original title bar provided by the system is still there, but it can't be seen by anyone just because it's covered by another window. I admit it's a good solution in such cases but for our library it's not appropriate because the code complexity will blow up. +## Special Thanks + +*Ordered by first contribution time* + +- [Yuhang Zhao](https://github.com/wangwenx190): Help me create this project. This project is mainly based on his code. +- [Julien](https://github.com/JulienMaille): Help me test this library on many various environments and help me fix the bugs we found. Contributed many code to improve this library. The MainWindow example is mostly based on his code. +- [Altair Wei](https://github.com/altairwei): Help me fix quite some small bugs and give me many important suggestions, the 2.x version is also inspired by his idea during our discussions. +- [Kenji Mouri](https://github.com/MouriNaruto): Give me a lot of help on Win32 native developing. +- [Dylan Liu](https://github.com/mentalfl0w): Help me improve the build process on macOS. +- [SineStriker](https://github.com/SineStriker): He spent almost a whole week helping me improve the Snap Layout implementation, fix potential bugs and give me a lot of useful suggestions. Without his great effort, the new implementation may never come. + ## License ```text diff --git a/examples/dialog/dialog.cpp b/examples/dialog/dialog.cpp index f4b8258d..92159a7d 100644 --- a/examples/dialog/dialog.cpp +++ b/examples/dialog/dialog.cpp @@ -56,11 +56,13 @@ void Dialog::setupUi() setWindowTitle(windowTitle() + FRAMELESSHELPER_STRING_LITERAL(" [%1]").arg(name)); }); +#if FRAMELESSHELPER_CONFIG(titlebar) titleBar = new StandardTitleBar(this); titleBar->setWindowIconVisible(true); -#ifndef Q_OS_MACOS +# if (!defined(Q_OS_MACOS) && FRAMELESSHELPER_CONFIG(system_button)) titleBar->maximizeButton()->hide(); -#endif // Q_OS_MACOS +# endif +#endif label = new QLabel(tr("Find &what:")); lineEdit = new QLineEdit; @@ -122,18 +124,22 @@ void Dialog::setupUi() QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSizeConstraint(QLayout::SetFixedSize); +#if FRAMELESSHELPER_CONFIG(titlebar) mainLayout->addWidget(titleBar); +#endif mainLayout->addLayout(controlsLayout); extension->hide(); +#if FRAMELESSHELPER_CONFIG(titlebar) FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); helper->setTitleBarWidget(titleBar); -#ifndef Q_OS_MACOS +# if (!defined(Q_OS_MACOS) && FRAMELESSHELPER_CONFIG(system_button)) helper->setSystemButton(titleBar->minimizeButton(), SystemButtonType::Minimize); helper->setSystemButton(titleBar->maximizeButton(), SystemButtonType::Maximize); helper->setSystemButton(titleBar->closeButton(), SystemButtonType::Close); -#endif // Q_OS_MACOS +# endif +#endif } void Dialog::waitReady() diff --git a/examples/dialog/dialog.h b/examples/dialog/dialog.h index 7b28cdaa..dd3d40d4 100644 --- a/examples/dialog/dialog.h +++ b/examples/dialog/dialog.h @@ -5,6 +5,8 @@ #include +FRAMELESSHELPER_REQUIRE_CONFIG(window) + QT_BEGIN_NAMESPACE class QCheckBox; class QDialogButtonBox; @@ -14,9 +16,11 @@ class QLineEdit; class QPushButton; QT_END_NAMESPACE +#if FRAMELESSHELPER_CONFIG(titlebar) FRAMELESSHELPER_BEGIN_NAMESPACE class StandardTitleBar; FRAMELESSHELPER_END_NAMESPACE +#endif class Dialog : public FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessDialog) { @@ -36,7 +40,9 @@ class Dialog : public FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessDialog) void setupUi(); private: +#if FRAMELESSHELPER_CONFIG(titlebar) FRAMELESSHELPER_PREPEND_NAMESPACE(StandardTitleBar) *titleBar = nullptr; +#endif QLabel *label = nullptr; QLineEdit *lineEdit = nullptr; QCheckBox *caseCheckBox = nullptr; diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp index c4aad197..84ff809f 100644 --- a/examples/mainwindow/mainwindow.cpp +++ b/examples/mainwindow/mainwindow.cpp @@ -70,8 +70,10 @@ void MainWindow::closeEvent(QCloseEvent *event) void MainWindow::initialize() { +#if FRAMELESSHELPER_CONFIG(titlebar) m_titleBar = new StandardTitleBar(this); m_titleBar->setTitleLabelAlignment(Qt::AlignCenter); +#endif m_mainWindow = new Ui::MainWindow; m_mainWindow->setupUi(this); @@ -94,19 +96,23 @@ QMenuBar::item:pressed { background: #888888; } )")); + +#if FRAMELESSHELPER_CONFIG(titlebar) const auto titleBarLayout = static_cast(m_titleBar->layout()); titleBarLayout->insertWidget(0, mb); - // setMenuWidget(): make the menu widget become the first row of the window. setMenuWidget(m_titleBar); +#endif +#if FRAMELESSHELPER_CONFIG(titlebar) FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); helper->setTitleBarWidget(m_titleBar); -#ifndef Q_OS_MACOS +# if (!defined(Q_OS_MACOS) && FRAMELESSHELPER_CONFIG(system_button)) helper->setSystemButton(m_titleBar->minimizeButton(), SystemButtonType::Minimize); helper->setSystemButton(m_titleBar->maximizeButton(), SystemButtonType::Maximize); helper->setSystemButton(m_titleBar->closeButton(), SystemButtonType::Close); -#endif // Q_OS_MACOS +# endif +#endif helper->setHitTestVisible(mb); // IMPORTANT! setWindowTitle(tr("FramelessHelper demo application - QMainWindow")); diff --git a/examples/mainwindow/mainwindow.h b/examples/mainwindow/mainwindow.h index 56c85904..b20d98a7 100644 --- a/examples/mainwindow/mainwindow.h +++ b/examples/mainwindow/mainwindow.h @@ -26,9 +26,13 @@ #include +FRAMELESSHELPER_REQUIRE_CONFIG(window) + +#if FRAMELESSHELPER_CONFIG(titlebar) FRAMELESSHELPER_BEGIN_NAMESPACE class StandardTitleBar; FRAMELESSHELPER_END_NAMESPACE +#endif namespace Ui { @@ -53,6 +57,8 @@ class MainWindow : public FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessMainWindow) void initialize(); private: +#if FRAMELESSHELPER_CONFIG(titlebar) FRAMELESSHELPER_PREPEND_NAMESPACE(StandardTitleBar) *m_titleBar = nullptr; +#endif Ui::MainWindow *m_mainWindow = nullptr; }; diff --git a/examples/quick/main.cpp b/examples/quick/main.cpp index 387089f6..7aa84a2b 100644 --- a/examples/quick/main.cpp +++ b/examples/quick/main.cpp @@ -38,6 +38,8 @@ #endif #include "../shared/log.h" +FRAMELESSHELPER_REQUIRE_CONFIG(window) + FRAMELESSHELPER_USE_NAMESPACE static constexpr const bool IS_MACOS_HOST = diff --git a/examples/widget/widget.cpp b/examples/widget/widget.cpp index 826d9ca8..e4ec297d 100644 --- a/examples/widget/widget.cpp +++ b/examples/widget/widget.cpp @@ -83,8 +83,10 @@ void Widget::initialize() setWindowTitle(tr("FramelessHelper demo application - QWidget")); setWindowIcon(QFileIconProvider().icon(QFileIconProvider::Computer)); resize(800, 600); +#if FRAMELESSHELPER_CONFIG(titlebar) m_titleBar = new StandardTitleBar(this); m_titleBar->setWindowIconVisible(true); +#endif m_clockLabel = new QLabel(this); m_clockLabel->setFrameShape(QFrame::NoFrame); QFont clockFont = font(); @@ -100,7 +102,9 @@ void Widget::initialize() const auto mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(0); mainLayout->setContentsMargins(0, 0, 0, 0); +#if FRAMELESSHELPER_CONFIG(titlebar) mainLayout->addWidget(m_titleBar); +#endif mainLayout->addLayout(contentLayout); updateStyleSheet(); @@ -131,13 +135,15 @@ void Widget::initialize() setWindowTitle(windowTitle() + FRAMELESSHELPER_STRING_LITERAL(" [%1]").arg(name)); }); +#if FRAMELESSHELPER_CONFIG(titlebar) FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); helper->setTitleBarWidget(m_titleBar); -#ifndef Q_OS_MACOS +# if (!defined(Q_OS_MACOS) && FRAMELESSHELPER_CONFIG(system_button)) helper->setSystemButton(m_titleBar->minimizeButton(), SystemButtonType::Minimize); helper->setSystemButton(m_titleBar->maximizeButton(), SystemButtonType::Maximize); helper->setSystemButton(m_titleBar->closeButton(), SystemButtonType::Close); -#endif // Q_OS_MACOS +# endif +#endif } void Widget::updateStyleSheet() diff --git a/examples/widget/widget.h b/examples/widget/widget.h index 4acd1716..463839e6 100644 --- a/examples/widget/widget.h +++ b/examples/widget/widget.h @@ -26,14 +26,18 @@ #include +FRAMELESSHELPER_REQUIRE_CONFIG(window) + QT_BEGIN_NAMESPACE class QLabel; class QShortcut; QT_END_NAMESPACE +#if FRAMELESSHELPER_CONFIG(titlebar) FRAMELESSHELPER_BEGIN_NAMESPACE class StandardTitleBar; FRAMELESSHELPER_END_NAMESPACE +#endif class Widget : public FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessWidget) { @@ -57,8 +61,10 @@ private Q_SLOTS: void updateStyleSheet(); private: - QLabel *m_clockLabel = nullptr; +#if FRAMELESSHELPER_CONFIG(titlebar) FRAMELESSHELPER_PREPEND_NAMESPACE(StandardTitleBar) *m_titleBar = nullptr; +#endif + QLabel *m_clockLabel = nullptr; QShortcut *m_fullScreenShortcut = nullptr; QShortcut *m_cancelShortcut = nullptr; }; diff --git a/include/FramelessHelper/Core/framelesshelpercore_global.h b/include/FramelessHelper/Core/framelesshelpercore_global.h index 74a2054a..34e58712 100644 --- a/include/FramelessHelper/Core/framelesshelpercore_global.h +++ b/include/FramelessHelper/Core/framelesshelpercore_global.h @@ -194,7 +194,7 @@ QT_END_NAMESPACE #endif #ifndef FRAMELESSHELPER_REQUIRE_CONFIG -# define FRAMELESSHELPER_REQUIRE_CONFIG(feature) static_assert(FRAMELESSHELPER_FEATURE_##feature == 1, "Required feature " #feature " for file " __FILE__ " is not available!") +# define FRAMELESSHELPER_REQUIRE_CONFIG(feature) static_assert(FRAMELESSHELPER_FEATURE_##feature == 1, "Required feature " #feature " for file " __FILE__ " is not available!"); #endif #ifndef FRAMELESSHELPER_CLASS_INFO diff --git a/src/quick/quickstandardtitlebar.cpp b/src/quick/quickstandardtitlebar.cpp index 5f527a3d..972d1d4a 100644 --- a/src/quick/quickstandardtitlebar.cpp +++ b/src/quick/quickstandardtitlebar.cpp @@ -103,9 +103,9 @@ void QuickStandardTitleBar::setTitleLabelAlignment(const Qt::Alignment value) } else if (m_labelAlignment & Qt::AlignRight) { #ifdef Q_OS_MACOS labelAnchors->setRight(titleBarPriv->right()); -#else // !Q_OS_MACOS +#elif FRAMELESSHELPER_CONFIG(system_button) labelAnchors->setRight(QQuickItemPrivate::get(m_systemButtonsRow)->left()); -#endif // Q_OS_MACOS +#endif labelAnchors->setRightMargin(kDefaultTitleBarContentsMargin); m_windowTitleLabel->setHAlign(QQuickLabel::AlignRight); } else if (m_labelAlignment & Qt::AlignHCenter) { diff --git a/src/widgets/standardtitlebar.cpp b/src/widgets/standardtitlebar.cpp index 00c56633..27f4d90d 100644 --- a/src/widgets/standardtitlebar.cpp +++ b/src/widgets/standardtitlebar.cpp @@ -524,9 +524,9 @@ void StandardTitleBar::paintEvent(QPaintEvent *event) x = (d->windowIconRect().right() + kDefaultTitleBarContentsMargin); } else if (d->labelAlignment & Qt::AlignRight) { x = (titleBarWidth - kDefaultTitleBarContentsMargin - labelSize.width); -#ifndef Q_OS_MACOS +#if (!defined(Q_OS_MACOS) && FRAMELESSHELPER_CONFIG(system_button)) x -= (titleBarWidth - d->minimizeButton->x()); -#endif // Q_OS_MACOS +#endif } else if (d->labelAlignment & Qt::AlignHCenter) { x = std::round(qreal(titleBarWidth - labelSize.width) / qreal(2)); } else {