Merge pull request #8 from stdware/stylesupport
Stylesupport
16个文件已修改
10个文件已添加
1 文件已重命名
| | |
| | | option(QWINDOWKIT_BUILD_STATIC "Build static libraries" OFF) |
| | | option(QWINDOWKIT_BUILD_WIDGETS "Build widgets module" ON) |
| | | option(QWINDOWKIT_BUILD_QUICK "Build quick module" ON) |
| | | option(QWINDOWKIT_BUILD_STYLESUPPORT "Build style support module" OFF) |
| | | option(QWINDOWKIT_BUILD_EXAMPLES "Build examples" OFF) |
| | | option(QWINDOWKIT_BUILD_DOCUMENTATIONS "Build documentations" OFF) |
| | | option(QWINDOWKIT_INSTALL "Install library" ON) |
| | |
| | | qwk_add_example(${PROJECT_NAME} |
| | | SOURCES ${_src} mainwindow.qrc ../shared/resources/shared.qrc |
| | | QT_LINKS Core Gui Widgets |
| | | LINKS QWKWidgets WidgetFrame |
| | | LINKS QWKWidgets QWKStyleSupport WidgetFrame |
| | | ) |
| | | |
| | | set_target_properties(${PROJECT_NAME} PROPERTIES |
| | |
| | | |
| | | /* Window */ |
| | | |
| | | MainWindow { |
| | | MainWindow[custom-style=true] { |
| | | background-color: transparent; |
| | | } |
| | | |
| | | MainWindow[custom-style=false] { |
| | | background-color: #1E1E1E; |
| | | } |
| | | |
| | |
| | | |
| | | /* Window */ |
| | | |
| | | MainWindow { |
| | | MainWindow[custom-style=true] { |
| | | background-color: transparent; |
| | | } |
| | | |
| | | MainWindow[custom-style=false] { |
| | | background-color: #F3F3F3; |
| | | } |
| | | |
| | |
| | | #include <QtCore/QFile> |
| | | #include <QtCore/QTime> |
| | | #include <QtCore/QTimer> |
| | | #include <QtGui/QPainter> |
| | | #include <QtGui/QActionGroup> |
| | | #include <QtWidgets/QApplication> |
| | | #include <QtWidgets/QStyle> |
| | | #include <QtWidgets/QPushButton> |
| | | |
| | | #include <QWKWidgets/widgetwindowagent.h> |
| | | #include <QWKStyleSupport/styleagent.h> |
| | | |
| | | #include <widgetframe/windowbar.h> |
| | | #include <widgetframe/windowbutton.h> |
| | |
| | | |
| | | MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { |
| | | installWindowAgent(); |
| | | |
| | | styleAgent = new QWK::StyleAgent(this); |
| | | |
| | | auto clockWidget = new ClockWidget(); |
| | | clockWidget->setObjectName(QStringLiteral("clock-widget")); |
| | |
| | | |
| | | void MainWindow::installWindowAgent() { |
| | | // 1. Setup window agent |
| | | auto agent = new QWK::WidgetWindowAgent(this); |
| | | agent->setup(this); |
| | | windowAgent = new QWK::WidgetWindowAgent(this); |
| | | windowAgent->setup(this); |
| | | |
| | | // 2. Construct your title bar |
| | | auto menuBar = [this, agent]() { |
| | | auto menuBar = [this]() { |
| | | auto menuBar = new QMenuBar(); |
| | | |
| | | // Virtual menu |
| | |
| | | edit->addAction(new QAction(tr("Redo(&R)"), menuBar)); |
| | | |
| | | // Theme action |
| | | auto darkAction = new QAction(tr("Dark Theme"), menuBar); |
| | | auto darkAction = new QAction(tr("Enable dark theme"), menuBar); |
| | | darkAction->setCheckable(true); |
| | | connect(darkAction, &QAction::triggered, this, [this](bool checked) { |
| | | loadStyleSheet(checked ? Dark : Light); // |
| | |
| | | darkAction->setChecked(currentTheme == Dark); // |
| | | }); |
| | | |
| | | #ifdef Q_OS_WIN |
| | | auto dwmBlurAction = new QAction(tr("Enable DWM blur"), menuBar); |
| | | dwmBlurAction->setCheckable(true); |
| | | connect(dwmBlurAction, &QAction::triggered, this, [this](bool checked){ |
| | | QWindow *w = windowHandle(); |
| | | styleAgent->setWindowAttribute(w, QStringLiteral("dwm-blur"), checked); |
| | | setProperty("custom-style", checked); |
| | | style()->polish(this); |
| | | }); |
| | | |
| | | auto acrylicAction = new QAction(tr("Enable acrylic material"), menuBar); |
| | | acrylicAction->setCheckable(true); |
| | | connect(acrylicAction, &QAction::triggered, this, [this](bool checked){ |
| | | QWindow *w = windowHandle(); |
| | | styleAgent->setWindowAttribute(w, QStringLiteral("acrylic-material"), QColor()); |
| | | setProperty("custom-style", checked); |
| | | style()->polish(this); |
| | | }); |
| | | |
| | | auto micaAction = new QAction(tr("Enable mica"), menuBar); |
| | | micaAction->setCheckable(true); |
| | | connect(micaAction, &QAction::triggered, this, [this](bool checked){ |
| | | QWindow *w = windowHandle(); |
| | | styleAgent->setWindowAttribute(w, QStringLiteral("mica"), checked); |
| | | setProperty("custom-style", checked); |
| | | style()->polish(this); |
| | | }); |
| | | |
| | | auto micaAltAction = new QAction(tr("Enable mica alt"), menuBar); |
| | | micaAltAction->setCheckable(true); |
| | | connect(micaAltAction, &QAction::triggered, this, [this](bool checked){ |
| | | QWindow *w = windowHandle(); |
| | | styleAgent->setWindowAttribute(w, QStringLiteral("mica-alt"), checked); |
| | | setProperty("custom-style", checked); |
| | | style()->polish(this); |
| | | }); |
| | | |
| | | auto winStyleGroup = new QActionGroup(menuBar); |
| | | winStyleGroup->addAction(dwmBlurAction); |
| | | winStyleGroup->addAction(acrylicAction); |
| | | winStyleGroup->addAction(micaAction); |
| | | winStyleGroup->addAction(micaAltAction); |
| | | #endif |
| | | |
| | | // Real menu |
| | | auto settings = new QMenu(tr("Settings(&S)"), menuBar); |
| | | settings->addAction(darkAction); |
| | | #ifdef Q_OS_WIN |
| | | settings->addSeparator(); |
| | | settings->addAction(dwmBlurAction); |
| | | settings->addAction(acrylicAction); |
| | | settings->addAction(micaAction); |
| | | settings->addAction(micaAltAction); |
| | | #endif |
| | | |
| | | menuBar->addMenu(file); |
| | | menuBar->addMenu(edit); |
| | |
| | | windowBar->setTitleLabel(titleLabel); |
| | | windowBar->setHostWidget(this); |
| | | |
| | | agent->setTitleBar(windowBar); |
| | | windowAgent->setTitleBar(windowBar); |
| | | #ifndef Q_OS_MAC |
| | | agent->setSystemButton(QWK::WindowAgentBase::WindowIcon, iconButton); |
| | | agent->setSystemButton(QWK::WindowAgentBase::Minimize, minButton); |
| | | agent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton); |
| | | agent->setSystemButton(QWK::WindowAgentBase::Close, closeButton); |
| | | windowAgent->setSystemButton(QWK::WindowAgentBase::WindowIcon, iconButton); |
| | | windowAgent->setSystemButton(QWK::WindowAgentBase::Minimize, minButton); |
| | | windowAgent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton); |
| | | windowAgent->setSystemButton(QWK::WindowAgentBase::Close, closeButton); |
| | | #endif |
| | | agent->setHitTestVisible(menuBar, true); |
| | | windowAgent->setHitTestVisible(menuBar, true); |
| | | |
| | | setMenuWidget(windowBar); |
| | | |
| | | // 3. Adds simulated mouse events to the title bar buttons |
| | | #ifdef Q_OS_WINDOWS |
| | | // Emulate Window system menu button behaviors |
| | | connect(iconButton, &QAbstractButton::clicked, agent, [iconButton, agent] { |
| | | connect(iconButton, &QAbstractButton::clicked, windowAgent, [this, iconButton] { |
| | | iconButton->setProperty("double-click-close", false); |
| | | |
| | | // Pick a suitable time threshold |
| | | QTimer::singleShot(75, agent, [iconButton, agent]() { |
| | | QTimer::singleShot(75, windowAgent, [this, iconButton]() { |
| | | if (iconButton->property("double-click-close").toBool()) |
| | | return; |
| | | agent->showSystemMenu(iconButton->mapToGlobal(QPoint{0, iconButton->height()})); |
| | | windowAgent->showSystemMenu(iconButton->mapToGlobal(QPoint{0, iconButton->height()})); |
| | | }); |
| | | }); |
| | | connect(iconButton, &QWK::WindowButton::doubleClicked, this, [iconButton, this]() { |
| | |
| | | setStyleSheet(QString::fromUtf8(qss.readAll())); |
| | | Q_EMIT themeChanged(); |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | #include <QtWidgets/QMainWindow> |
| | | |
| | | namespace QWK { |
| | | class WidgetWindowAgent; |
| | | class StyleAgent; |
| | | } |
| | | |
| | | class MainWindow : public QMainWindow { |
| | | Q_OBJECT |
| | | public: |
| | |
| | | void loadStyleSheet(Theme theme); |
| | | |
| | | Theme currentTheme{}; |
| | | |
| | | QWK::WidgetWindowAgent *windowAgent; |
| | | QWK::StyleAgent *styleAgent; |
| | | }; |
| | | |
| | | #endif // MAINWINDOW_H |
| | |
| | | set(QMAKE_QWK_CORE_NAME_RELEASE QWKCore) |
| | | set(QMAKE_QWK_WIDGETS_NAME_RELEASE QWKWidgets) |
| | | set(QMAKE_QWK_QUICK_NAME_RELEASE QWKQuick) |
| | | set(QMAKE_QWK_STYLESUPPORT_NAME_RELEASE QWKStyleSupport) |
| | | |
| | | set(QMAKE_QWK_CORE_NAME_DEBUG QWKCore${CMAKE_DEBUG_POSTFIX}) |
| | | set(QMAKE_QWK_WIDGETS_NAME_DEBUG QWKWidgets${CMAKE_DEBUG_POSTFIX}) |
| | | set(QMAKE_QWK_QUICK_NAME_DEBUG QWKQuick${CMAKE_DEBUG_POSTFIX}) |
| | | set(QMAKE_QWK_STYLESUPPORT_NAME_DEBUG QWKStyleSupport${CMAKE_DEBUG_POSTFIX}) |
| | | |
| | | file(GLOB _qmake_components "${CMAKE_CURRENT_LIST_DIR}/qmake/*.pri.in") |
| | | |
| | |
| | | QWKCore${CMAKE_DEBUG_POSTFIX}.lib |
| | | QWKWidgets${CMAKE_DEBUG_POSTFIX}.lib |
| | | QWKQuick${CMAKE_DEBUG_POSTFIX}.lib |
| | | QWKStyleSupport${CMAKE_DEBUG_POSTFIX}.lib |
| | | ) |
| | | |
| | | set(MSBUILD_QWK_LIBRARY_LIST_RELEASE |
| | | QWKCore.lib |
| | | QWKWidgets.lib |
| | | QWKQuick.lib |
| | | QWKStyleSupport.lib |
| | | ) |
| | | |
| | | to_dos_separator(MSBUILD_QWK_INSTALL_PREFIX) |
New file |
| | |
| | | !defined(QMAKE_QWK_STYLESUPPORT_INCLUDED, var) { |
| | | QMAKE_QWK_STYLESUPPORT_INCLUDED = 1 |
| | | |
| | | include($$PWD/QWKCore.pri) |
| | | |
| | | CONFIG(debug, debug|release) { |
| | | LIBS += -l@QMAKE_QWK_STYLESUPPORT_NAME_DEBUG@ |
| | | } else { |
| | | LIBS += -l@QMAKE_QWK_STYLESUPPORT_NAME_RELEASE@ |
| | | } |
| | | } |
| | |
| | | add_subdirectory(quick) |
| | | endif() |
| | | |
| | | if(QWINDOWKIT_BUILD_STYLESUPPORT) |
| | | add_subdirectory(stylesupport) |
| | | endif() |
| | | |
| | | # ---------------------------------- |
| | | # Documentation |
| | | # ---------------------------------- |
| | |
| | | windowitemdelegate.cpp |
| | | kernel/nativeeventfilter_p.h |
| | | kernel/nativeeventfilter.cpp |
| | | kernel/systemwindow_p.h |
| | | shared/systemwindow_p.h |
| | | contexts/abstractwindowcontext_p.h |
| | | contexts/abstractwindowcontext.cpp |
| | | ) |
| | | |
| | | set(_defines_private) |
| | | set(_links_private) |
| | | |
| | | if(WIN32) |
| | | list(APPEND _src |
| | | qwindowkit_windows.h |
| | | qwindowkit_windows.cpp |
| | | shared/qwkwindowsextra_p.h |
| | | ) |
| | | elseif(APPLE) |
| | | list(APPEND _links_private |
| | | "-framework Foundation" |
| | | "-framework Cocoa" |
| | | "-framework AppKit" |
| | | ) |
| | | else() |
| | | list(APPEND _src |
| | | qwindowkit_linux.h |
| | |
| | | contexts/cocoawindowcontext_p.h |
| | | contexts/cocoawindowcontext.mm |
| | | ) |
| | | list(APPEND _links_private |
| | | "-framework Foundation" |
| | | "-framework Cocoa" |
| | | "-framework AppKit" |
| | | ) |
| | | else() |
| | | list(APPEND _src |
| | | contexts/qtwindowcontext_p.h |
| | |
| | | |
| | | qwk_add_library(${PROJECT_NAME} AUTOGEN |
| | | SOURCES ${_src} |
| | | DEFINES_PRIVATE ${_defines_private} |
| | | LINKS |
| | | LINKS_PRIVATE ${_links_private} |
| | | QT_LINKS Core Gui |
| | | QT_INCLUDE_PRIVATE Core Gui |
| | | INCLUDE_PRIVATE kernel contexts platforms |
| | | INCLUDE_PRIVATE kernel contexts shared |
| | | PREFIX QWK_CORE |
| | | ) |
| | | |
| | |
| | | #include <QtGui/QPen> |
| | | #include <QtGui/QPainter> |
| | | #include <QtGui/QScreen> |
| | | #include <memory> |
| | | |
| | | #include "qwkglobal_p.h" |
| | | |
| | | namespace QWK { |
| | | |
| | | class WinIdChangeEventFilter : public QObject { |
| | | public: |
| | | explicit WinIdChangeEventFilter(QObject *widget, AbstractWindowContext *ctx, |
| | | QObject *parent = nullptr) |
| | | : QObject(parent), ctx(ctx) { |
| | | widget->installEventFilter(this); |
| | | } |
| | | |
| | | protected: |
| | | bool eventFilter(QObject *obj, QEvent *event) override { |
| | | Q_UNUSED(obj) |
| | | if (event->type() == QEvent::WinIdChange) { |
| | | ctx->notifyWinIdChange(); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | protected: |
| | | AbstractWindowContext *ctx; |
| | | }; |
| | | |
| | | AbstractWindowContext::AbstractWindowContext() = default; |
| | | |
| | |
| | | } |
| | | m_host = host; |
| | | m_delegate.reset(delegate); |
| | | m_winIdChangeEventFilter = std::make_unique<WinIdChangeEventFilter>(host, this); |
| | | |
| | | m_windowHandle = m_delegate->hostWindow(m_host); |
| | | if (m_windowHandle) { |
| | | winIdChanged(); |
| | | } |
| | | } |
| | | |
| | | bool AbstractWindowContext::setWindowAttribute(const QString &key, const QVariant &attribute) { |
| | | auto it = m_windowAttributes.find(key); |
| | | if (it.value() == attribute) |
| | | return true; |
| | | |
| | | auto newVar = attribute; |
| | | auto oldVar = it.value(); |
| | | bool res = false; |
| | | void *args[] = { |
| | | &const_cast<QString &>(key), |
| | | &newVar, |
| | | &oldVar, |
| | | &res, |
| | | }; |
| | | virtual_hook(WindowAttributeChangedHook, args); |
| | | if (res) { |
| | | it.value() = newVar; |
| | | } |
| | | return res; |
| | | } |
| | | |
| | | bool AbstractWindowContext::setHitTestVisible(const QObject *obj, bool visible) { |
| | |
| | | if (m_titleBar == item) { |
| | | return false; |
| | | } |
| | | |
| | | if (m_titleBar) { |
| | | // Since the title bar is changed, all items inside it should be dereferenced right away |
| | | for (auto &button : m_systemButtons) { |
| | | button = nullptr; |
| | | } |
| | | m_hitTestVisibleItems.clear(); |
| | | } |
| | | |
| | | m_titleBar = item; |
| | | return true; |
| | | } |
| | |
| | | inline QWindow *window() const; |
| | | inline WindowItemDelegate *delegate() const; |
| | | |
| | | inline QVariant windowAttribute(const QString &key) const; |
| | | bool setWindowAttribute(const QString &key, const QVariant &attribute); |
| | | |
| | | inline bool isHitTestVisible(const QObject *obj) const; |
| | | bool setHitTestVisible(const QObject *obj, bool visible); |
| | | |
| | |
| | | RaiseWindowHook, |
| | | ShowSystemMenuHook, |
| | | DefaultColorsHook, |
| | | WindowAttributeChangedHook, |
| | | DrawWindows10BorderHook, // Only works on Windows 10 |
| | | SystemButtonAreaChangedHook, // Only works on Mac |
| | | }; |
| | |
| | | QObject *m_titleBar{}; |
| | | std::array<QObject *, WindowAgentBase::NumSystemButton> m_systemButtons{}; |
| | | |
| | | QVariantHash m_windowAttributes; |
| | | std::unique_ptr<QObject> m_winIdChangeEventFilter; |
| | | }; |
| | | |
| | | inline QObject *AbstractWindowContext::host() const { |
| | |
| | | |
| | | inline WindowItemDelegate *AbstractWindowContext::delegate() const { |
| | | return m_delegate.get(); |
| | | } |
| | | |
| | | inline QVariant AbstractWindowContext::windowAttribute(const QString &key) const { |
| | | return m_windowAttributes.value(key); |
| | | } |
| | | |
| | | inline bool AbstractWindowContext::isHitTestVisible(const QObject *obj) const { |
| | |
| | | return; |
| | | } |
| | | |
| | | case WindowAttributeChangedHook: { |
| | | auto args = static_cast<void **>(data); |
| | | const auto &key = *static_cast<const QString *>(args[0]); |
| | | const auto &newVar = *static_cast<const QVariant *>(args[1]); |
| | | const auto &oldVar = *static_cast<const QVariant *>(args[2]); |
| | | auto &res = *static_cast<bool *>(args[3]); |
| | | |
| | | if (key == QStringLiteral("no-system-buttons")) { |
| | | if (newVar.toBool()) { |
| | | // TODO: set off |
| | | } else { |
| | | // TODO: set on |
| | | } |
| | | res = true; |
| | | } |
| | | return; |
| | | } |
| | | |
| | | default: |
| | | break; |
| | | } |
| | |
| | | #include <QtGui/QPalette> |
| | | #include <QtGui/QStyleHints> |
| | | |
| | | #include <QtCore/private/qwinregistry_p.h> |
| | | #include <QtCore/private/qsystemlibrary_p.h> |
| | | #include <QtGui/private/qhighdpiscaling_p.h> |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | | # include <QtGui/private/qguiapplication_p.h> |
| | |
| | | # include <QtGui/qpa/qplatformwindow_p.h> |
| | | #endif |
| | | |
| | | #include <shellscalingapi.h> |
| | | #include <dwmapi.h> |
| | | #include <timeapi.h> |
| | | |
| | | #include "qwkglobal_p.h" |
| | | #include "qwkwindowsextra_p.h" |
| | | |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | | Q_DECLARE_METATYPE(QMargins) |
| | | #endif |
| | | |
| | | namespace QWK { |
| | | |
| | | enum _DWMWINDOWATTRIBUTE { |
| | | // [set] BOOL, Allows the use of host backdrop brushes for the window. |
| | | _DWMWA_USE_HOSTBACKDROPBRUSH = 17, |
| | | |
| | | // Undocumented, the same with DWMWA_USE_IMMERSIVE_DARK_MODE, but available on systems |
| | | // before Win10 20H1. |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19, |
| | | |
| | | // [set] BOOL, Allows a window to either use the accent color, or dark, according to the |
| | | // user Color Mode preferences. |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE = 20, |
| | | |
| | | // [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners |
| | | _DWMWA_WINDOW_CORNER_PREFERENCE = 33, |
| | | |
| | | // [get] UINT, width of the visible border around a thick frame window |
| | | _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37, |
| | | |
| | | // [get, set] SYSTEMBACKDROP_TYPE, Controls the system-drawn backdrop material of a window, |
| | | // including behind the non-client area. |
| | | _DWMWA_SYSTEMBACKDROP_TYPE = 38, |
| | | |
| | | // Undocumented, use this value to enable Mica material on Win11 21H2. You should use |
| | | // DWMWA_SYSTEMBACKDROP_TYPE instead on Win11 22H2 and newer. |
| | | _DWMWA_MICA_EFFECT = 1029 |
| | | }; |
| | | |
| | | // Types used with DWMWA_SYSTEMBACKDROP_TYPE |
| | | enum _DWM_SYSTEMBACKDROP_TYPE { |
| | | _DWMSBT_AUTO, // [Default] Let DWM automatically decide the system-drawn backdrop for this window. |
| | | _DWMSBT_NONE, // [Disable] Do not draw any system backdrop. |
| | | _DWMSBT_MAINWINDOW, // [Mica] Draw the backdrop material effect corresponding to a long-lived window. |
| | | _DWMSBT_TRANSIENTWINDOW, // [Acrylic] Draw the backdrop material effect corresponding to a transient window. |
| | | _DWMSBT_TABBEDWINDOW, // [Mica Alt] Draw the backdrop material effect corresponding to a window with a tabbed title bar. |
| | | }; |
| | | |
| | | enum WINDOWCOMPOSITIONATTRIB { |
| | | WCA_UNDEFINED = 0, |
| | | WCA_NCRENDERING_ENABLED = 1, |
| | | WCA_NCRENDERING_POLICY = 2, |
| | | WCA_TRANSITIONS_FORCEDISABLED = 3, |
| | | WCA_ALLOW_NCPAINT = 4, |
| | | WCA_CAPTION_BUTTON_BOUNDS = 5, |
| | | WCA_NONCLIENT_RTL_LAYOUT = 6, |
| | | WCA_FORCE_ICONIC_REPRESENTATION = 7, |
| | | WCA_EXTENDED_FRAME_BOUNDS = 8, |
| | | WCA_HAS_ICONIC_BITMAP = 9, |
| | | WCA_THEME_ATTRIBUTES = 10, |
| | | WCA_NCRENDERING_EXILED = 11, |
| | | WCA_NCADORNMENTINFO = 12, |
| | | WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, |
| | | WCA_VIDEO_OVERLAY_ACTIVE = 14, |
| | | WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, |
| | | WCA_DISALLOW_PEEK = 16, |
| | | WCA_CLOAK = 17, |
| | | WCA_CLOAKED = 18, |
| | | WCA_ACCENT_POLICY = 19, |
| | | WCA_FREEZE_REPRESENTATION = 20, |
| | | WCA_EVER_UNCLOAKED = 21, |
| | | WCA_VISUAL_OWNER = 22, |
| | | WCA_HOLOGRAPHIC = 23, |
| | | WCA_EXCLUDED_FROM_DDA = 24, |
| | | WCA_PASSIVEUPDATEMODE = 25, |
| | | WCA_USEDARKMODECOLORS = 26, |
| | | WCA_CORNER_STYLE = 27, |
| | | WCA_PART_COLOR = 28, |
| | | WCA_DISABLE_MOVESIZE_FEEDBACK = 29, |
| | | WCA_LAST = 30 |
| | | }; |
| | | |
| | | enum ACCENT_STATE { |
| | | ACCENT_DISABLED = 0, |
| | | ACCENT_ENABLE_GRADIENT = 1, |
| | | ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, |
| | | ACCENT_ENABLE_BLURBEHIND = 3, // Traditional DWM blur |
| | | ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, // RS4 1803 |
| | | ACCENT_ENABLE_HOST_BACKDROP = 5, // RS5 1809 |
| | | ACCENT_INVALID_STATE = 6 // Using this value will remove the window background |
| | | }; |
| | | |
| | | enum ACCENT_FLAG { |
| | | ACCENT_NONE = 0, |
| | | ACCENT_ENABLE_ACRYLIC = 1, |
| | | ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY = 482 |
| | | }; |
| | | |
| | | struct ACCENT_POLICY { |
| | | DWORD dwAccentState; |
| | | DWORD dwAccentFlags; |
| | | DWORD dwGradientColor; // #AABBGGRR |
| | | DWORD dwAnimationId; |
| | | }; |
| | | using PACCENT_POLICY = ACCENT_POLICY *; |
| | | |
| | | struct WINDOWCOMPOSITIONATTRIBDATA { |
| | | WINDOWCOMPOSITIONATTRIB Attrib; |
| | | PVOID pvData; |
| | | SIZE_T cbData; |
| | | }; |
| | | using PWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA *; |
| | | |
| | | using SetWindowCompositionAttributePtr = BOOL(WINAPI *)(HWND, PWINDOWCOMPOSITIONATTRIBDATA); |
| | | |
| | | // The thickness of an auto-hide taskbar in pixels. |
| | | static constexpr const quint8 kAutoHideTaskBarThickness = 2; |
| | |
| | | |
| | | // Original Qt window proc function |
| | | static WNDPROC g_qtWindowProc = nullptr; |
| | | |
| | | struct DynamicApis { |
| | | static const DynamicApis &instance() { |
| | | static const DynamicApis inst{}; |
| | | return inst; |
| | | } |
| | | |
| | | // template <typename T> |
| | | // struct DefaultFunc; |
| | | // |
| | | // template <typename Return, typename... Args> |
| | | // struct DefaultFunc<Return(QT_WIN_CALLBACK *)(Args...)> { |
| | | // static Return STDAPICALLTYPE func(Args...) { |
| | | // return Return{}; |
| | | // } |
| | | // }; |
| | | // |
| | | // #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = |
| | | // DefaultFunc<decltype(&::NAME)>::func |
| | | |
| | | #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr |
| | | |
| | | DYNAMIC_API_DECLARE(DwmFlush); |
| | | DYNAMIC_API_DECLARE(DwmIsCompositionEnabled); |
| | | DYNAMIC_API_DECLARE(DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_DECLARE(DwmGetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(DwmSetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(DwmExtendFrameIntoClientArea); |
| | | DYNAMIC_API_DECLARE(GetDpiForWindow); |
| | | DYNAMIC_API_DECLARE(GetSystemMetricsForDpi); |
| | | DYNAMIC_API_DECLARE(GetDpiForMonitor); |
| | | DYNAMIC_API_DECLARE(timeGetDevCaps); |
| | | DYNAMIC_API_DECLARE(timeBeginPeriod); |
| | | DYNAMIC_API_DECLARE(timeEndPeriod); |
| | | |
| | | #undef DYNAMIC_API_DECLARE |
| | | |
| | | SetWindowCompositionAttributePtr pSetWindowCompositionAttribute = nullptr; |
| | | |
| | | private: |
| | | DynamicApis() { |
| | | #define DYNAMIC_API_RESOLVE(DLL, NAME) \ |
| | | p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME)) |
| | | |
| | | QSystemLibrary user32(QStringLiteral("user32")); |
| | | DYNAMIC_API_RESOLVE(user32, GetDpiForWindow); |
| | | DYNAMIC_API_RESOLVE(user32, GetSystemMetricsForDpi); |
| | | DYNAMIC_API_RESOLVE(user32, SetWindowCompositionAttribute); |
| | | |
| | | QSystemLibrary shcore(QStringLiteral("shcore")); |
| | | DYNAMIC_API_RESOLVE(shcore, GetDpiForMonitor); |
| | | |
| | | QSystemLibrary dwmapi(QStringLiteral("dwmapi")); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmFlush); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmIsCompositionEnabled); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetWindowAttribute); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmSetWindowAttribute); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmExtendFrameIntoClientArea); |
| | | |
| | | QSystemLibrary winmm(QStringLiteral("winmm")); |
| | | DYNAMIC_API_RESOLVE(winmm, timeGetDevCaps); |
| | | DYNAMIC_API_RESOLVE(winmm, timeBeginPeriod); |
| | | DYNAMIC_API_RESOLVE(winmm, timeEndPeriod); |
| | | |
| | | #undef DYNAMIC_API_RESOLVE |
| | | } |
| | | |
| | | ~DynamicApis() = default; |
| | | |
| | | Q_DISABLE_COPY_MOVE(DynamicApis) |
| | | }; |
| | | |
| | | static inline constexpr bool operator==(const POINT &lhs, const POINT &rhs) noexcept { |
| | | return ((lhs.x == rhs.x) && (lhs.y == rhs.y)); |
| | | } |
| | | |
| | | static inline constexpr bool operator!=(const POINT &lhs, const POINT &rhs) noexcept { |
| | | return !operator==(lhs, rhs); |
| | | } |
| | | |
| | | static inline constexpr bool operator==(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return ((lhs.cx == rhs.cx) && (lhs.cy == rhs.cy)); |
| | | } |
| | | |
| | | static inline constexpr bool operator!=(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return !operator==(lhs, rhs); |
| | | } |
| | | |
| | | static inline constexpr bool operator>(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return ((lhs.cx * lhs.cy) > (rhs.cx * rhs.cy)); |
| | | } |
| | | |
| | | static inline constexpr bool operator>=(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return (operator>(lhs, rhs) || operator==(lhs, rhs)); |
| | | } |
| | | |
| | | static inline constexpr bool operator<(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return (operator!=(lhs, rhs) && !operator>(lhs, rhs)); |
| | | } |
| | | |
| | | static inline constexpr bool operator<=(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return (operator<(lhs, rhs) || operator==(lhs, rhs)); |
| | | } |
| | | |
| | | static inline constexpr bool operator==(const RECT &lhs, const RECT &rhs) noexcept { |
| | | return ((lhs.left == rhs.left) && (lhs.top == rhs.top) && (lhs.right == rhs.right) && |
| | | (lhs.bottom == rhs.bottom)); |
| | | } |
| | | |
| | | static inline constexpr bool operator!=(const RECT &lhs, const RECT &rhs) noexcept { |
| | | return !operator==(lhs, rhs); |
| | | } |
| | | |
| | | static inline constexpr QPoint point2qpoint(const POINT &point) { |
| | | return QPoint{int(point.x), int(point.y)}; |
| | | } |
| | | |
| | | static inline constexpr POINT qpoint2point(const QPoint &point) { |
| | | return POINT{LONG(point.x()), LONG(point.y())}; |
| | | } |
| | | |
| | | static inline constexpr QSize size2qsize(const SIZE &size) { |
| | | return QSize{int(size.cx), int(size.cy)}; |
| | | } |
| | | |
| | | static inline constexpr SIZE qsize2size(const QSize &size) { |
| | | return SIZE{LONG(size.width()), LONG(size.height())}; |
| | | } |
| | | |
| | | static inline constexpr QRect rect2qrect(const RECT &rect) { |
| | | return QRect{ |
| | | QPoint{int(rect.left), int(rect.top) }, |
| | | QSize{int(RECT_WIDTH(rect)), int(RECT_HEIGHT(rect))} |
| | | }; |
| | | } |
| | | |
| | | static inline constexpr RECT qrect2rect(const QRect &qrect) { |
| | | return RECT{LONG(qrect.left()), LONG(qrect.top()), LONG(qrect.right()), |
| | | LONG(qrect.bottom())}; |
| | | } |
| | | |
| | | static inline /*constexpr*/ QString hwnd2str(const WId windowId) { |
| | | // NULL handle is allowed here. |
| | | return QLatin1String("0x") + |
| | | QString::number(windowId, 16).toUpper().rightJustified(8, u'0'); |
| | | } |
| | | |
| | | static inline /*constexpr*/ QString hwnd2str(HWND hwnd) { |
| | | // NULL handle is allowed here. |
| | | return hwnd2str(reinterpret_cast<WId>(hwnd)); |
| | | } |
| | | |
| | | static inline bool isWin8OrGreater() { |
| | | static const bool result = IsWindows8OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin8Point1OrGreater() { |
| | | static const bool result = IsWindows8Point1OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin10OrGreater() { |
| | | static const bool result = IsWindows10OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin11OrGreater() { |
| | | static const bool result = IsWindows11OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin1122H2OrGreater() { |
| | | static const bool result = IsWindows1122H2OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isDwmCompositionEnabled() { |
| | | if (isWin8OrGreater()) { |
| | | return true; |
| | | } |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (!apis.pDwmIsCompositionEnabled) { |
| | | return false; |
| | | } |
| | | BOOL enabled = FALSE; |
| | | return SUCCEEDED(apis.pDwmIsCompositionEnabled(&enabled)) && enabled; |
| | | } |
| | | |
| | | static inline bool isWindowFrameBorderColorized() { |
| | | const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); |
| | | if (!registry.isValid()) { |
| | | return false; |
| | | } |
| | | const auto value = registry.dwordValue(L"ColorPrevalence"); |
| | | if (!value.second) { |
| | | return false; |
| | | } |
| | | return value.first; |
| | | } |
| | | |
| | | static inline bool isDarkThemeActive() { |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) |
| | | return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark; |
| | | #else |
| | | const QWinRegistryKey registry( |
| | | HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"); |
| | | if (!registry.isValid()) { |
| | | return false; |
| | | } |
| | | const auto value = registry.dwordValue(L"AppsUseLightTheme"); |
| | | if (!value.second) { |
| | | return false; |
| | | } |
| | | return !value.first; |
| | | #endif |
| | | } |
| | | |
| | | static inline bool isDarkWindowFrameEnabled(HWND hwnd) { |
| | | BOOL enabled = FALSE; |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE, &enabled, |
| | | sizeof(enabled)))) { |
| | | return enabled; |
| | | } else if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, |
| | | &enabled, sizeof(enabled)))) { |
| | | return enabled; |
| | | } else { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | static inline QColor getAccentColor() { |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) |
| | | return QGuiApplication::palette().color(QPalette::Accent); |
| | | #else |
| | | const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); |
| | | if (!registry.isValid()) { |
| | | return {}; |
| | | } |
| | | const auto value = registry.dwordValue(L"AccentColor"); |
| | | if (!value.second) { |
| | | return {}; |
| | | } |
| | | // The retrieved value is in the #AABBGGRR format, we need to |
| | | // convert it to the #AARRGGBB format which Qt expects. |
| | | const QColor abgr = QColor::fromRgba(value.first); |
| | | if (!abgr.isValid()) { |
| | | return {}; |
| | | } |
| | | return QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha()); |
| | | #endif |
| | | } |
| | | |
| | | static inline void triggerFrameChange(HWND hwnd) { |
| | | ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, |
| | |
| | | #endif |
| | | showSystemMenu2(hWnd, qpoint2point(nativeGlobalPos), false, |
| | | m_delegate->isHostSizeFixed(m_host)); |
| | | return; |
| | | } |
| | | |
| | | case WindowAttributeChangedHook: { |
| | | auto args = static_cast<void **>(data); |
| | | const auto &key = *static_cast<const QString *>(args[0]); |
| | | const auto &newVar = *static_cast<const QVariant *>(args[1]); |
| | | const auto &oldVar = *static_cast<const QVariant *>(args[2]); |
| | | auto &res = *static_cast<bool *>(args[3]); |
| | | |
| | | if (!windowId) |
| | | return; |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | |
| | | if (key == QStringLiteral("frame-shadow")) { |
| | | if (newVar.toBool()) { |
| | | // TODO: set off |
| | | } else { |
| | | // TODO: set on |
| | | } |
| | | } else if (key == QStringLiteral("mica")) { |
| | | if (!isWin11OrGreater()) { |
| | | return; |
| | | } |
| | | if (newVar.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | if (isWin1122H2OrGreater()) { |
| | | // Use official DWM API to enable Mica, available since Windows 11 22H2 |
| | | // (10.0.22621). |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_MAINWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, |
| | | &backdropType, sizeof(backdropType)); |
| | | } else { |
| | | // Use undocumented DWM API to enable Mica, available since Windows 11 |
| | | // (10.0.22000). |
| | | const BOOL enable = TRUE; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, |
| | | sizeof(enable)); |
| | | } |
| | | } else { |
| | | if (isWin1122H2OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, |
| | | &backdropType, sizeof(backdropType)); |
| | | } else { |
| | | const BOOL enable = FALSE; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, |
| | | sizeof(enable)); |
| | | } |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | } else if (key == QStringLiteral("mica-alt")) { |
| | | if (!isWin1122H2OrGreater()) { |
| | | return; |
| | | } |
| | | if (newVar.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | // Use official DWM API to enable Mica Alt, available since Windows 11 22H2 |
| | | // (10.0.22621). |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TABBEDWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | } else { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | res = true; |
| | | } else if (key == QStringLiteral("acrylic-material")) { |
| | | if (newVar.type() == QVariant::Color) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | if (isWin11OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TRANSIENTWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, |
| | | &backdropType, sizeof(backdropType)); |
| | | } else { |
| | | auto gradientColor = newVar.value<QColor>(); |
| | | |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND; |
| | | policy.dwAccentFlags = ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY; |
| | | // This API expects the #AABBGGRR format. |
| | | policy.dwGradientColor = |
| | | DWORD(qRgba(gradientColor.blue(), gradientColor.green(), |
| | | gradientColor.red(), gradientColor.alpha())); |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } |
| | | } else { |
| | | if (isWin11OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, |
| | | &backdropType, sizeof(backdropType)); |
| | | } else { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_DISABLED; |
| | | policy.dwAccentFlags = ACCENT_NONE; |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | res = true; |
| | | } |
| | | return; |
| | | } |
| | | |
New file |
| | |
| | | #ifndef QWKWINDOWSEXTRA_P_H |
| | | #define QWKWINDOWSEXTRA_P_H |
| | | |
| | | // |
| | | // W A R N I N G !!! |
| | | // ----------------- |
| | | // |
| | | // This file is not part of the QWindowKit API. It is used purely as an |
| | | // implementation detail. This header file may change from version to |
| | | // version without notice, or may even be removed. |
| | | // |
| | | |
| | | #include <shellscalingapi.h> |
| | | #include <dwmapi.h> |
| | | #include <timeapi.h> |
| | | |
| | | #include <QWKCore/qwindowkit_windows.h> |
| | | |
| | | #include <QtCore/private/qsystemlibrary_p.h> |
| | | #include <QtCore/private/qwinregistry_p.h> |
| | | |
| | | #include <QtGui/QStyleHints> |
| | | #include <QtGui/QPalette> |
| | | |
| | | // Don't include this header in any header files. |
| | | |
| | | namespace QWK { |
| | | |
| | | enum _DWMWINDOWATTRIBUTE { |
| | | // [set] BOOL, Allows the use of host backdrop brushes for the window. |
| | | _DWMWA_USE_HOSTBACKDROPBRUSH = 17, |
| | | |
| | | // Undocumented, the same with DWMWA_USE_IMMERSIVE_DARK_MODE, but available on systems |
| | | // before Win10 20H1. |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19, |
| | | |
| | | // [set] BOOL, Allows a window to either use the accent color, or dark, according to the |
| | | // user Color Mode preferences. |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE = 20, |
| | | |
| | | // [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners |
| | | _DWMWA_WINDOW_CORNER_PREFERENCE = 33, |
| | | |
| | | // [get] UINT, width of the visible border around a thick frame window |
| | | _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37, |
| | | |
| | | // [get, set] SYSTEMBACKDROP_TYPE, Controls the system-drawn backdrop material of a window, |
| | | // including behind the non-client area. |
| | | _DWMWA_SYSTEMBACKDROP_TYPE = 38, |
| | | |
| | | // Undocumented, use this value to enable Mica material on Win11 21H2. You should use |
| | | // DWMWA_SYSTEMBACKDROP_TYPE instead on Win11 22H2 and newer. |
| | | _DWMWA_MICA_EFFECT = 1029 |
| | | }; |
| | | |
| | | // Types used with DWMWA_SYSTEMBACKDROP_TYPE |
| | | enum _DWM_SYSTEMBACKDROP_TYPE { |
| | | _DWMSBT_AUTO, // [Default] Let DWM automatically decide the system-drawn backdrop for this |
| | | // window. |
| | | _DWMSBT_NONE, // [Disable] Do not draw any system backdrop. |
| | | _DWMSBT_MAINWINDOW, // [Mica] Draw the backdrop material effect corresponding to a |
| | | // long-lived window. |
| | | _DWMSBT_TRANSIENTWINDOW, // [Acrylic] Draw the backdrop material effect corresponding to a |
| | | // transient window. |
| | | _DWMSBT_TABBEDWINDOW, // [Mica Alt] Draw the backdrop material effect corresponding to a |
| | | // window with a tabbed title bar. |
| | | }; |
| | | |
| | | enum WINDOWCOMPOSITIONATTRIB { |
| | | WCA_UNDEFINED = 0, |
| | | WCA_NCRENDERING_ENABLED = 1, |
| | | WCA_NCRENDERING_POLICY = 2, |
| | | WCA_TRANSITIONS_FORCEDISABLED = 3, |
| | | WCA_ALLOW_NCPAINT = 4, |
| | | WCA_CAPTION_BUTTON_BOUNDS = 5, |
| | | WCA_NONCLIENT_RTL_LAYOUT = 6, |
| | | WCA_FORCE_ICONIC_REPRESENTATION = 7, |
| | | WCA_EXTENDED_FRAME_BOUNDS = 8, |
| | | WCA_HAS_ICONIC_BITMAP = 9, |
| | | WCA_THEME_ATTRIBUTES = 10, |
| | | WCA_NCRENDERING_EXILED = 11, |
| | | WCA_NCADORNMENTINFO = 12, |
| | | WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, |
| | | WCA_VIDEO_OVERLAY_ACTIVE = 14, |
| | | WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, |
| | | WCA_DISALLOW_PEEK = 16, |
| | | WCA_CLOAK = 17, |
| | | WCA_CLOAKED = 18, |
| | | WCA_ACCENT_POLICY = 19, |
| | | WCA_FREEZE_REPRESENTATION = 20, |
| | | WCA_EVER_UNCLOAKED = 21, |
| | | WCA_VISUAL_OWNER = 22, |
| | | WCA_HOLOGRAPHIC = 23, |
| | | WCA_EXCLUDED_FROM_DDA = 24, |
| | | WCA_PASSIVEUPDATEMODE = 25, |
| | | WCA_USEDARKMODECOLORS = 26, |
| | | WCA_CORNER_STYLE = 27, |
| | | WCA_PART_COLOR = 28, |
| | | WCA_DISABLE_MOVESIZE_FEEDBACK = 29, |
| | | WCA_LAST = 30 |
| | | }; |
| | | |
| | | enum ACCENT_STATE { |
| | | ACCENT_DISABLED = 0, |
| | | ACCENT_ENABLE_GRADIENT = 1, |
| | | ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, |
| | | ACCENT_ENABLE_BLURBEHIND = 3, // Traditional DWM blur |
| | | ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, // RS4 1803 |
| | | ACCENT_ENABLE_HOST_BACKDROP = 5, // RS5 1809 |
| | | ACCENT_INVALID_STATE = 6 // Using this value will remove the window background |
| | | }; |
| | | |
| | | enum ACCENT_FLAG { |
| | | ACCENT_NONE = 0, |
| | | ACCENT_ENABLE_ACRYLIC = 1, |
| | | ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY = 482 |
| | | }; |
| | | |
| | | struct ACCENT_POLICY { |
| | | DWORD dwAccentState; |
| | | DWORD dwAccentFlags; |
| | | DWORD dwGradientColor; // #AABBGGRR |
| | | DWORD dwAnimationId; |
| | | }; |
| | | using PACCENT_POLICY = ACCENT_POLICY *; |
| | | |
| | | struct WINDOWCOMPOSITIONATTRIBDATA { |
| | | WINDOWCOMPOSITIONATTRIB Attrib; |
| | | PVOID pvData; |
| | | SIZE_T cbData; |
| | | }; |
| | | using PWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA *; |
| | | |
| | | using SetWindowCompositionAttributePtr = BOOL(WINAPI *)(HWND, PWINDOWCOMPOSITIONATTRIBDATA); |
| | | |
| | | namespace { |
| | | |
| | | struct DynamicApis { |
| | | static const DynamicApis &instance() { |
| | | static const DynamicApis inst{}; |
| | | return inst; |
| | | } |
| | | |
| | | #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr |
| | | |
| | | DYNAMIC_API_DECLARE(DwmFlush); |
| | | DYNAMIC_API_DECLARE(DwmIsCompositionEnabled); |
| | | DYNAMIC_API_DECLARE(DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_DECLARE(DwmGetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(DwmSetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(DwmExtendFrameIntoClientArea); |
| | | DYNAMIC_API_DECLARE(DwmEnableBlurBehindWindow); |
| | | DYNAMIC_API_DECLARE(GetDpiForWindow); |
| | | DYNAMIC_API_DECLARE(GetSystemMetricsForDpi); |
| | | DYNAMIC_API_DECLARE(GetDpiForMonitor); |
| | | DYNAMIC_API_DECLARE(timeGetDevCaps); |
| | | DYNAMIC_API_DECLARE(timeBeginPeriod); |
| | | DYNAMIC_API_DECLARE(timeEndPeriod); |
| | | |
| | | #undef DYNAMIC_API_DECLARE |
| | | |
| | | SetWindowCompositionAttributePtr pSetWindowCompositionAttribute = nullptr; |
| | | |
| | | private: |
| | | DynamicApis() { |
| | | #define DYNAMIC_API_RESOLVE(DLL, NAME) \ |
| | | p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME)) |
| | | |
| | | QSystemLibrary user32(QStringLiteral("user32")); |
| | | DYNAMIC_API_RESOLVE(user32, GetDpiForWindow); |
| | | DYNAMIC_API_RESOLVE(user32, GetSystemMetricsForDpi); |
| | | DYNAMIC_API_RESOLVE(user32, SetWindowCompositionAttribute); |
| | | |
| | | QSystemLibrary shcore(QStringLiteral("shcore")); |
| | | DYNAMIC_API_RESOLVE(shcore, GetDpiForMonitor); |
| | | |
| | | QSystemLibrary dwmapi(QStringLiteral("dwmapi")); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmFlush); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmIsCompositionEnabled); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetWindowAttribute); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmSetWindowAttribute); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmExtendFrameIntoClientArea); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmEnableBlurBehindWindow); |
| | | |
| | | QSystemLibrary winmm(QStringLiteral("winmm")); |
| | | DYNAMIC_API_RESOLVE(winmm, timeGetDevCaps); |
| | | DYNAMIC_API_RESOLVE(winmm, timeBeginPeriod); |
| | | DYNAMIC_API_RESOLVE(winmm, timeEndPeriod); |
| | | |
| | | #undef DYNAMIC_API_RESOLVE |
| | | } |
| | | |
| | | ~DynamicApis() = default; |
| | | |
| | | Q_DISABLE_COPY_MOVE(DynamicApis) |
| | | }; |
| | | |
| | | } |
| | | |
| | | static inline constexpr bool operator==(const POINT &lhs, const POINT &rhs) noexcept { |
| | | return ((lhs.x == rhs.x) && (lhs.y == rhs.y)); |
| | | } |
| | | |
| | | static inline constexpr bool operator!=(const POINT &lhs, const POINT &rhs) noexcept { |
| | | return !operator==(lhs, rhs); |
| | | } |
| | | |
| | | static inline constexpr bool operator==(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return ((lhs.cx == rhs.cx) && (lhs.cy == rhs.cy)); |
| | | } |
| | | |
| | | static inline constexpr bool operator!=(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return !operator==(lhs, rhs); |
| | | } |
| | | |
| | | static inline constexpr bool operator>(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return ((lhs.cx * lhs.cy) > (rhs.cx * rhs.cy)); |
| | | } |
| | | |
| | | static inline constexpr bool operator>=(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return (operator>(lhs, rhs) || operator==(lhs, rhs)); |
| | | } |
| | | |
| | | static inline constexpr bool operator<(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return (operator!=(lhs, rhs) && !operator>(lhs, rhs)); |
| | | } |
| | | |
| | | static inline constexpr bool operator<=(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return (operator<(lhs, rhs) || operator==(lhs, rhs)); |
| | | } |
| | | |
| | | static inline constexpr bool operator==(const RECT &lhs, const RECT &rhs) noexcept { |
| | | return ((lhs.left == rhs.left) && (lhs.top == rhs.top) && (lhs.right == rhs.right) && |
| | | (lhs.bottom == rhs.bottom)); |
| | | } |
| | | |
| | | static inline constexpr bool operator!=(const RECT &lhs, const RECT &rhs) noexcept { |
| | | return !operator==(lhs, rhs); |
| | | } |
| | | |
| | | static inline constexpr QPoint point2qpoint(const POINT &point) { |
| | | return QPoint{int(point.x), int(point.y)}; |
| | | } |
| | | |
| | | static inline constexpr POINT qpoint2point(const QPoint &point) { |
| | | return POINT{LONG(point.x()), LONG(point.y())}; |
| | | } |
| | | |
| | | static inline constexpr QSize size2qsize(const SIZE &size) { |
| | | return QSize{int(size.cx), int(size.cy)}; |
| | | } |
| | | |
| | | static inline constexpr SIZE qsize2size(const QSize &size) { |
| | | return SIZE{LONG(size.width()), LONG(size.height())}; |
| | | } |
| | | |
| | | static inline constexpr QRect rect2qrect(const RECT &rect) { |
| | | return QRect{ |
| | | QPoint{int(rect.left), int(rect.top) }, |
| | | QSize{int(RECT_WIDTH(rect)), int(RECT_HEIGHT(rect))} |
| | | }; |
| | | } |
| | | |
| | | static inline constexpr RECT qrect2rect(const QRect &qrect) { |
| | | return RECT{LONG(qrect.left()), LONG(qrect.top()), LONG(qrect.right()), |
| | | LONG(qrect.bottom())}; |
| | | } |
| | | |
| | | static inline /*constexpr*/ QString hwnd2str(const WId windowId) { |
| | | // NULL handle is allowed here. |
| | | return QLatin1String("0x") + |
| | | QString::number(windowId, 16).toUpper().rightJustified(8, u'0'); |
| | | } |
| | | |
| | | static inline /*constexpr*/ QString hwnd2str(HWND hwnd) { |
| | | // NULL handle is allowed here. |
| | | return hwnd2str(reinterpret_cast<WId>(hwnd)); |
| | | } |
| | | |
| | | static inline bool isWin8OrGreater() { |
| | | static const bool result = IsWindows8OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin8Point1OrGreater() { |
| | | static const bool result = IsWindows8Point1OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin10OrGreater() { |
| | | static const bool result = IsWindows10OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin11OrGreater() { |
| | | static const bool result = IsWindows11OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin1122H2OrGreater() { |
| | | static const bool result = IsWindows1122H2OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isDwmCompositionEnabled() { |
| | | if (isWin8OrGreater()) { |
| | | return true; |
| | | } |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (!apis.pDwmIsCompositionEnabled) { |
| | | return false; |
| | | } |
| | | BOOL enabled = FALSE; |
| | | return SUCCEEDED(apis.pDwmIsCompositionEnabled(&enabled)) && enabled; |
| | | } |
| | | |
| | | static inline bool isWindowFrameBorderColorized() { |
| | | const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); |
| | | if (!registry.isValid()) { |
| | | return false; |
| | | } |
| | | const auto value = registry.dwordValue(L"ColorPrevalence"); |
| | | if (!value.second) { |
| | | return false; |
| | | } |
| | | return value.first; |
| | | } |
| | | |
| | | static inline bool isDarkThemeActive() { |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) |
| | | return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark; |
| | | #else |
| | | const QWinRegistryKey registry( |
| | | HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"); |
| | | if (!registry.isValid()) { |
| | | return false; |
| | | } |
| | | const auto value = registry.dwordValue(L"AppsUseLightTheme"); |
| | | if (!value.second) { |
| | | return false; |
| | | } |
| | | return !value.first; |
| | | #endif |
| | | } |
| | | |
| | | static inline bool isDarkWindowFrameEnabled(HWND hwnd) { |
| | | BOOL enabled = FALSE; |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE, &enabled, |
| | | sizeof(enabled)))) { |
| | | return enabled; |
| | | } else if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, |
| | | &enabled, sizeof(enabled)))) { |
| | | return enabled; |
| | | } else { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | static inline QColor getAccentColor() { |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) |
| | | return QGuiApplication::palette().color(QPalette::Accent); |
| | | #else |
| | | const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); |
| | | if (!registry.isValid()) { |
| | | return {}; |
| | | } |
| | | const auto value = registry.dwordValue(L"AccentColor"); |
| | | if (!value.second) { |
| | | return {}; |
| | | } |
| | | // The retrieved value is in the #AABBGGRR format, we need to |
| | | // convert it to the #AARRGGBB format which Qt expects. |
| | | const QColor abgr = QColor::fromRgba(value.first); |
| | | if (!abgr.isValid()) { |
| | | return {}; |
| | | } |
| | | return QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha()); |
| | | #endif |
| | | } |
| | | |
| | | } |
| | | |
| | | #endif // QWKWINDOWSEXTRA_P_H |
| | |
| | | |
| | | WindowAgentBase::~WindowAgentBase() = default; |
| | | |
| | | QVariant WindowAgentBase::windowAttribute(const QString &key) const { |
| | | Q_D(const WindowAgentBase); |
| | | return d->context->windowAttribute(key); |
| | | } |
| | | |
| | | bool WindowAgentBase::setWindowAttribute(const QString &key, const QVariant &attribute) { |
| | | Q_D(WindowAgentBase); |
| | | return d->context->setWindowAttribute(key, attribute); |
| | | } |
| | | |
| | | void WindowAgentBase::showSystemMenu(const QPoint &pos) { |
| | | Q_D(WindowAgentBase); |
| | | d->context->showSystemMenu(pos); |
| | |
| | | }; |
| | | Q_ENUM(SystemButton) |
| | | |
| | | QVariant windowAttribute(const QString &key) const; |
| | | bool setWindowAttribute(const QString &key, const QVariant &attribute); |
| | | |
| | | public Q_SLOTS: |
| | | void showSystemMenu(const QPoint &pos); // Only available on Windows now |
| | | void centralize(); |
New file |
| | |
| | | project(QWKStyleSupport |
| | | VERSION ${QWINDOWKIT_VERSION} |
| | | LANGUAGES CXX |
| | | ) |
| | | |
| | | set(_src |
| | | qwkstylesupportglobal.h |
| | | styleagent.h |
| | | styleagent_p.h |
| | | styleagent.cpp |
| | | ) |
| | | |
| | | set(_links_private) |
| | | |
| | | if(WIN32) |
| | | list(APPEND _src |
| | | styleagent_win.cpp |
| | | ) |
| | | elseif(APPLE) |
| | | list(APPEND _links_private |
| | | "-framework Foundation" |
| | | "-framework Cocoa" |
| | | "-framework AppKit" |
| | | ) |
| | | list(APPEND _src |
| | | styleagent_mac.cpp |
| | | ) |
| | | else() |
| | | list(APPEND _src |
| | | styleagent_linux.cpp |
| | | ) |
| | | endif() |
| | | |
| | | qwk_add_library(${PROJECT_NAME} AUTOGEN |
| | | SOURCES ${_src} |
| | | LINKS QWKCore |
| | | LINKS_PRIVATE ${_links_private} |
| | | QT_LINKS Core Gui |
| | | QT_INCLUDE_PRIVATE Core Gui |
| | | PREFIX QWK_STYLESUPPORT |
| | | ) |
| | | |
| | | set_target_properties(${PROJECT_NAME} PROPERTIES |
| | | CXX_STANDARD 17 |
| | | CXX_STANDARD_REQUIRED TRUE |
| | | ) |
| | | |
| | | set(QWINDOWKIT_ENABLED_TARGETS ${QWINDOWKIT_ENABLED_TARGETS} ${PROJECT_NAME} PARENT_SCOPE) |
| | | set(QWINDOWKIT_ENABLED_SUBDIRECTORIES ${QWINDOWKIT_ENABLED_SUBDIRECTORIES} stylesupport PARENT_SCOPE) |
New file |
| | |
| | | #ifndef QWKSTYLESUPPORTGLOBAL_H |
| | | #define QWKSTYLESUPPORTGLOBAL_H |
| | | |
| | | #include <QtCore/QtGlobal> |
| | | |
| | | #ifndef QWK_STYLESUPPORT_EXPORT |
| | | # ifdef QWK_STYLESUPPORT_STATIC |
| | | # define QWK_STYLESUPPORT_EXPORT |
| | | # else |
| | | # ifdef QWK_STYLESUPPORT_LIBRARY |
| | | # define QWK_STYLESUPPORT_EXPORT Q_DECL_EXPORT |
| | | # else |
| | | # define QWK_STYLESUPPORT_EXPORT Q_DECL_IMPORT |
| | | # endif |
| | | # endif |
| | | #endif |
| | | |
| | | #endif // QWKSTYLESUPPORTGLOBAL_H |
New file |
| | |
| | | #include "styleagent.h" |
| | | #include "styleagent_p.h" |
| | | |
| | | #include <QtCore/QVariant> |
| | | |
| | | namespace QWK { |
| | | |
| | | StyleAgentPrivate::StyleAgentPrivate() { |
| | | } |
| | | |
| | | StyleAgentPrivate::~StyleAgentPrivate() = default; |
| | | |
| | | void StyleAgentPrivate::init() { |
| | | } |
| | | |
| | | void StyleAgentPrivate::notifyThemeChanged(StyleAgent::SystemTheme theme) { |
| | | if (theme == systemTheme) |
| | | return; |
| | | systemTheme = theme; |
| | | |
| | | Q_Q(StyleAgent); |
| | | Q_EMIT q->systemThemeChanged(); |
| | | } |
| | | |
| | | void StyleAgentPrivate::_q_windowDestroyed() { |
| | | windowAttributes.remove(static_cast<QWindow *>(sender())); |
| | | } |
| | | |
| | | StyleAgent::StyleAgent(QObject *parent) : StyleAgent(*new StyleAgentPrivate(), parent) { |
| | | Q_D(StyleAgent); |
| | | d->setupSystemThemeHook(); |
| | | } |
| | | |
| | | StyleAgent::~StyleAgent() { |
| | | Q_D(StyleAgent); |
| | | d->removeSystemThemeHook(); |
| | | } |
| | | |
| | | StyleAgent::SystemTheme StyleAgent::systemTheme() const { |
| | | Q_D(const StyleAgent); |
| | | return d->systemTheme; |
| | | } |
| | | |
| | | QVariant StyleAgent::windowAttribute(QWindow *window, const QString &key) const { |
| | | Q_D(const StyleAgent); |
| | | return d->windowAttributes.value(window).value(key); |
| | | } |
| | | |
| | | bool StyleAgent::setWindowAttribute(QWindow *window, const QString &key, |
| | | const QVariant &attribute) { |
| | | Q_D(StyleAgent); |
| | | if (!window) |
| | | return false; |
| | | |
| | | auto it = d->windowAttributes.find(window); |
| | | if (it == d->windowAttributes.end()) { |
| | | if (!attribute.isValid()) |
| | | return true; |
| | | if (!d->updateWindowAttribute(window, key, attribute, {})) |
| | | return false; |
| | | connect(window, &QWindow::destroyed, d, &StyleAgentPrivate::_q_windowDestroyed); |
| | | d->windowAttributes.insert(window, QVariantHash{ |
| | | {key, attribute} |
| | | }); |
| | | } else { |
| | | auto &attributes = it.value(); |
| | | auto oldAttribute = attributes.value(key); |
| | | if (oldAttribute == attribute) |
| | | return true; |
| | | if (!d->updateWindowAttribute(window, key, attribute, oldAttribute)) |
| | | return false; |
| | | attributes.insert(key, attribute); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | StyleAgent::StyleAgent(StyleAgentPrivate &d, QObject *parent) : QObject(parent), d_ptr(&d) { |
| | | d.q_ptr = this; |
| | | |
| | | d.init(); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | #ifndef STYLEAGENT_H |
| | | #define STYLEAGENT_H |
| | | |
| | | #include <memory> |
| | | |
| | | #include <QtCore/QObject> |
| | | #include <QtGui/QWindow> |
| | | |
| | | #include <QWKStyleSupport/qwkstylesupportglobal.h> |
| | | |
| | | namespace QWK { |
| | | |
| | | class StyleAgentPrivate; |
| | | |
| | | class QWK_STYLESUPPORT_EXPORT StyleAgent : public QObject { |
| | | Q_OBJECT |
| | | Q_DECLARE_PRIVATE(StyleAgent) |
| | | public: |
| | | explicit StyleAgent(QObject *parent = nullptr); |
| | | ~StyleAgent() override; |
| | | |
| | | enum SystemTheme { |
| | | Unknown, |
| | | Light, |
| | | Dark, |
| | | HighContrast, |
| | | }; |
| | | Q_ENUM(SystemTheme) |
| | | |
| | | public: |
| | | SystemTheme systemTheme() const; |
| | | |
| | | QVariant windowAttribute(QWindow *window, const QString &key) const; |
| | | bool setWindowAttribute(QWindow *window, const QString &key, const QVariant &attribute); |
| | | |
| | | Q_SIGNALS: |
| | | void systemThemeChanged(); // Do we need wallpaper change notify? |
| | | |
| | | protected: |
| | | StyleAgent(StyleAgentPrivate &d, QObject *parent = nullptr); |
| | | |
| | | const std::unique_ptr<StyleAgentPrivate> d_ptr; |
| | | }; |
| | | |
| | | } |
| | | |
| | | #endif // STYLEAGENT_H |
New file |
| | |
| | | #include "styleagent_p.h" |
| | | |
| | | #include <QtCore/QVariant> |
| | | |
| | | namespace QWK { |
| | | |
| | | void StyleAgentPrivate::setupSystemThemeHook() { |
| | | } |
| | | |
| | | void StyleAgentPrivate::removeSystemThemeHook() { |
| | | } |
| | | |
| | | bool StyleAgentPrivate::updateWindowAttribute(QWindow *window, const QString &key, |
| | | const QVariant &attribute, |
| | | const QVariant &oldAttribute) { |
| | | Q_UNUSED(oldAttribute) |
| | | return false; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | #include "styleagent_p.h" |
| | | |
| | | #include <QtCore/QVariant> |
| | | |
| | | namespace QWK { |
| | | |
| | | void StyleAgentPrivate::setupSystemThemeHook() { |
| | | } |
| | | |
| | | void StyleAgentPrivate::removeSystemThemeHook() { |
| | | } |
| | | |
| | | bool StyleAgentPrivate::updateWindowAttribute(QWindow *window, const QString &key, |
| | | const QVariant &attribute, |
| | | const QVariant &oldAttribute) { |
| | | Q_UNUSED(oldAttribute) |
| | | |
| | | if (key == QStringLiteral("no-system-buttons")) { |
| | | if (attribute.toBool()) { |
| | | // TODO: set off |
| | | } else { |
| | | // TODO: set on |
| | | } |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | #ifndef STYLEAGENTPRIVATE_H |
| | | #define STYLEAGENTPRIVATE_H |
| | | |
| | | // |
| | | // W A R N I N G !!! |
| | | // ----------------- |
| | | // |
| | | // This file is not part of the QWindowKit API. It is used purely as an |
| | | // implementation detail. This header file may change from version to |
| | | // version without notice, or may even be removed. |
| | | // |
| | | |
| | | #include <QWKStyleSupport/styleagent.h> |
| | | #include <QtCore/QHash> |
| | | |
| | | namespace QWK { |
| | | |
| | | class StyleAgentPrivate : public QObject { |
| | | Q_DECLARE_PUBLIC(StyleAgent) |
| | | public: |
| | | StyleAgentPrivate(); |
| | | ~StyleAgentPrivate() override; |
| | | |
| | | void init(); |
| | | |
| | | StyleAgent *q_ptr; |
| | | |
| | | StyleAgent::SystemTheme systemTheme = StyleAgent::Dark; |
| | | QHash<QWindow *, QVariantHash> windowAttributes; |
| | | |
| | | virtual void setupSystemThemeHook(); |
| | | virtual void removeSystemThemeHook(); |
| | | virtual bool updateWindowAttribute(QWindow *window, const QString &key, |
| | | const QVariant &attribute, const QVariant &oldAttribute); |
| | | |
| | | void notifyThemeChanged(StyleAgent::SystemTheme theme); |
| | | |
| | | private: |
| | | void _q_windowDestroyed(); |
| | | }; |
| | | |
| | | } |
| | | |
| | | #endif // STYLEAGENTPRIVATE_H |
New file |
| | |
| | | #include "styleagent_p.h" |
| | | |
| | | #include <QtCore/QSet> |
| | | #include <QtCore/QVariant> |
| | | #include <QtGui/QColor> |
| | | |
| | | #include <QWKCore/private/qwkwindowsextra_p.h> |
| | | #include <QWKCore/private/nativeeventfilter_p.h> |
| | | |
| | | namespace QWK { |
| | | |
| | | using StyleAgentSet = QSet<StyleAgentPrivate *>; |
| | | Q_GLOBAL_STATIC(StyleAgentSet, g_styleAgentSet) |
| | | |
| | | class SystemSettingEventFilter : public AppNativeEventFilter { |
| | | public: |
| | | bool nativeEventFilter(const QByteArray &eventType, void *message, |
| | | QT_NATIVE_EVENT_RESULT_TYPE *result) override { |
| | | Q_UNUSED(eventType) |
| | | if (!result) { |
| | | return false; |
| | | } |
| | | |
| | | const auto msg = static_cast<const MSG *>(message); |
| | | switch (msg->message) { |
| | | case WM_THEMECHANGED: |
| | | case WM_SYSCOLORCHANGE: |
| | | case WM_DWMCOLORIZATIONCOLORCHANGED: { |
| | | // TODO: walk through `g_styleAgentSet` |
| | | break; |
| | | } |
| | | |
| | | case WM_SETTINGCHANGE: { |
| | | if (!msg->wParam && msg->lParam && |
| | | std::wcscmp(reinterpret_cast<LPCWSTR>(msg->lParam), L"ImmersiveColorSet") == |
| | | 0) { |
| | | // TODO: walk through `g_styleAgentSet` |
| | | } |
| | | break; |
| | | } |
| | | |
| | | default: |
| | | break; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | static SystemSettingEventFilter *instance; |
| | | |
| | | static inline void install() { |
| | | if (instance) { |
| | | return; |
| | | } |
| | | instance = new SystemSettingEventFilter(); |
| | | } |
| | | |
| | | static inline void uninstall() { |
| | | if (!instance) { |
| | | return; |
| | | } |
| | | delete instance; |
| | | instance = nullptr; |
| | | } |
| | | }; |
| | | |
| | | SystemSettingEventFilter *SystemSettingEventFilter::instance = nullptr; |
| | | |
| | | void StyleAgentPrivate::setupSystemThemeHook() { |
| | | g_styleAgentSet->insert(this); |
| | | SystemSettingEventFilter::install(); |
| | | |
| | | // Initialize `systemTheme` variable |
| | | } |
| | | |
| | | void StyleAgentPrivate::removeSystemThemeHook() { |
| | | if (!g_styleAgentSet->remove(this)) |
| | | return; |
| | | |
| | | if (g_styleAgentSet->isEmpty()) { |
| | | SystemSettingEventFilter::uninstall(); |
| | | } |
| | | } |
| | | |
| | | bool StyleAgentPrivate::updateWindowAttribute(QWindow *window, const QString &key, |
| | | const QVariant &attribute, |
| | | const QVariant &oldAttribute) { |
| | | Q_UNUSED(oldAttribute) |
| | | |
| | | const auto hwnd = reinterpret_cast<HWND>(window->winId()); |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | |
| | | if (key == QStringLiteral("mica")) { |
| | | if (!isWin11OrGreater()) { |
| | | return false; |
| | | } |
| | | if (attribute.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | if (isWin1122H2OrGreater()) { |
| | | // Use official DWM API to enable Mica, available since Windows 11 22H2 |
| | | // (10.0.22621). |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_MAINWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | } else { |
| | | // Use undocumented DWM API to enable Mica, available since Windows 11 |
| | | // (10.0.22000). |
| | | const BOOL enable = TRUE; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); |
| | | } |
| | | } else { |
| | | if (isWin1122H2OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | } else { |
| | | const BOOL enable = FALSE; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); |
| | | } |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | return true; |
| | | } else if (key == QStringLiteral("mica-alt")) { |
| | | if (!isWin1122H2OrGreater()) { |
| | | return false; |
| | | } |
| | | if (attribute.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | // Use official DWM API to enable Mica Alt, available since Windows 11 22H2 |
| | | // (10.0.22621). |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TABBEDWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | } else { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | return true; |
| | | } else if (key == QStringLiteral("acrylic-material")) { |
| | | if (!isWin10OrGreater()) { |
| | | return false; |
| | | } |
| | | if (attribute.userType() == QMetaType::QColor) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | if (isWin11OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TRANSIENTWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | } else { |
| | | auto gradientColor = attribute.value<QColor>(); |
| | | |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND; |
| | | policy.dwAccentFlags = ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY; |
| | | // This API expects the #AABBGGRR format. |
| | | policy.dwGradientColor = |
| | | DWORD(qRgba(gradientColor.blue(), gradientColor.green(), |
| | | gradientColor.red(), gradientColor.alpha())); |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } |
| | | } else { |
| | | if (isWin11OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | } else { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_DISABLED; |
| | | policy.dwAccentFlags = ACCENT_NONE; |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | return true; |
| | | } else if (key == QStringLiteral("dwm-blur")) { |
| | | if (attribute.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | if (isWin8OrGreater()) { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND; |
| | | policy.dwAccentFlags = ACCENT_NONE; |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } else { |
| | | DWM_BLURBEHIND bb{}; |
| | | bb.fEnable = TRUE; |
| | | bb.fTransitionOnMaximized = TRUE; |
| | | bb.dwFlags = DWM_BB_ENABLE | DWM_BB_TRANSITIONONMAXIMIZED; |
| | | apis.pDwmEnableBlurBehindWindow(hwnd, &bb); |
| | | } |
| | | } else { |
| | | if (isWin8OrGreater()) { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_DISABLED; |
| | | policy.dwAccentFlags = ACCENT_NONE; |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } else { |
| | | DWM_BLURBEHIND bb{}; |
| | | bb.fEnable = FALSE; |
| | | bb.dwFlags = DWM_BB_ENABLE; |
| | | apis.pDwmEnableBlurBehindWindow(hwnd, &bb); |
| | | } |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | namespace QWK { |
| | | |
| | | class WidgetWinIdChangeEventFilter : public QObject { |
| | | public: |
| | | explicit WidgetWinIdChangeEventFilter(QWidget *widget, AbstractWindowContext *ctx) |
| | | : QObject(ctx), widget(widget), ctx(ctx) { |
| | | widget->installEventFilter(this); |
| | | } |
| | | |
| | | protected: |
| | | bool eventFilter(QObject *obj, QEvent *event) override { |
| | | Q_UNUSED(obj) |
| | | if (event->type() == QEvent::WinIdChange) { |
| | | ctx->notifyWinIdChange(); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | protected: |
| | | QWidget *widget; |
| | | AbstractWindowContext *ctx; |
| | | }; |
| | | |
| | | WidgetWindowAgentPrivate::WidgetWindowAgentPrivate() = default; |
| | | |
| | | WidgetWindowAgentPrivate::~WidgetWindowAgentPrivate() = default; |
| | |
| | | #ifdef Q_OS_WINDOWS |
| | | d->setupWindows10BorderWorkaround(); |
| | | #endif |
| | | std::ignore = new WidgetWinIdChangeEventFilter(w, d->context.get()); |
| | | |
| | | return true; |
| | | } |
| | | |