From 0f5e16464c1036bb22e8eb845a69f15ffb3b8156 Mon Sep 17 00:00:00 2001 From: jeffrey0326 <547913081@qq.com> Date: Wed, 21 Aug 2024 16:12:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81windows?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=AA=97=E5=8F=A3win7-win10(area=E3=80=81blu?= =?UTF-8?q?r)=E3=80=81win11(mica=E3=80=81mica-alt)=E6=95=88=E6=9E=9C?= =?UTF-8?q?=E5=88=87=E6=8D=A2=EF=BC=8C=E4=BF=AE=E6=94=B9=E5=A4=9C=E9=97=B4?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=88=87=E6=8D=A2=E5=8A=A8=E7=94=BB=E6=95=88?= =?UTF-8?q?=E6=9E=9C=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8A=A8=E7=94=BB=E6=89=93?= =?UTF-8?q?=E6=96=AD=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/example_en_US.ts | 45 ++- example/example_zh_CN.ts | 45 ++- example/qml/page/New_TableView.qml | 263 ++++++++++++++++++ example/qml/page/T_Theme.qml | 59 +++- example/qml/window/MainWindow.qml | 10 +- example/src/component/CircularReveal.cpp | 25 +- example/src/component/CircularReveal.h | 1 + src/FluFrameless.cpp | 243 +++++++++++++++- src/FluFrameless.h | 80 ++++++ .../imports/FluentUI/Controls/FluWindow.qml | 23 +- src/Qt5/imports/FluentUI/plugins.qmltypes | 10 + 11 files changed, 757 insertions(+), 47 deletions(-) create mode 100644 example/qml/page/New_TableView.qml diff --git a/example/example_en_US.ts b/example/example_en_US.ts index 1e7b9ce7..a1edb73f 100644 --- a/example/example_en_US.ts +++ b/example/example_en_US.ts @@ -639,7 +639,7 @@ - + Cancel @@ -659,57 +659,57 @@ - + Finish - + Next - + Previous - + Dark Mode - + Here you can switch to night mode. - + Hide Easter eggs - + Try a few more clicks!! - + Upgrade Tips - + FluentUI is currently up to date - + -- The current app version - + Now go and download the new version? @@ -718,17 +718,17 @@ Updated content: - + OK - + The current version is already the latest - + The network is abnormal @@ -2264,6 +2264,21 @@ Some contents... Open Blur Window + + + window tintOpacity + + + + + window blurRadius + + + + + window effect + + T_TimePicker diff --git a/example/example_zh_CN.ts b/example/example_zh_CN.ts index 88b8c04d..a5086d73 100644 --- a/example/example_zh_CN.ts +++ b/example/example_zh_CN.ts @@ -616,7 +616,7 @@ MainWindow - + Dark Mode 夜间模式 @@ -648,7 +648,7 @@ - + Cancel 取消 @@ -668,52 +668,52 @@ 搜索 - + Finish 完成 - + Next 下一步 - + Previous 上一步 - + Here you can switch to night mode. 在这里,您可以切换到夜间模式。 - + Hide Easter eggs 隐藏彩蛋 - + Try a few more clicks!! 再试几下!! - + Upgrade Tips 升级提示 - + FluentUI is currently up to date FluentUI 目前最新版本 - + -- The current app version -- 当前应用版本 - + Now go and download the new version? @@ -726,17 +726,17 @@ Updated content: - + OK 确定 - + The current version is already the latest 当前版本已经是最新版本 - + The network is abnormal 网络异常 @@ -2446,6 +2446,21 @@ Some contents... Open Blur Window + + + window tintOpacity + + + + + window blurRadius + + + + + window effect + + T_TimePicker diff --git a/example/qml/page/New_TableView.qml b/example/qml/page/New_TableView.qml new file mode 100644 index 00000000..0bef204a --- /dev/null +++ b/example/qml/page/New_TableView.qml @@ -0,0 +1,263 @@ +import QtQuick 2.15 +import FluentUI 1.0 +import "../component" + +FluPage{ + title:"表格测试" + TableView{ + columnSource: [ + { + title: "全选", + dataIndex: 'patientsex', + width:60, + readOnly:true, + position: 0, + delegate: com_checkbox, + headerDelegate: com_header_checkbox, + frozen: true + }, + + { + title: "检测日期", + dataIndex: 'testdate', + readOnly:true, + // width:150, + // delegate: com_action + }, + { + title: "条码号", + dataIndex: 'barcode', + width:80, + readOnly:true, + position: 0, + movable: false, + frozen: true + // delegate: com_checkbox, + // headerDelegate: com_header_checkbox + }, + { + title: "样本号", + dataIndex: 'sampleid', + width:100, + position: 1, + minimumWidth:100, + maximumWidth:100, + }, + { + title: "姓名", + dataIndex: 'patientname', + width:220, + minimumWidth:100, + maximumWidth:250 + }, + { + title:"操作", + dataIndex:"", + delegate:com_action, + frozen: true, + width: 250 + }, + { + title: "头像", + dataIndex: 'imageurl', + width:120, + minimumWidth:80, + maximumWidth:250, + delegate:com_avatar, + frozen: true + }, + { + title: "性别", + dataIndex: 'patientsex', + width:130, + minimumWidth:50, + maximumWidth:250, + // delegate:com_avatar + }, + + { + title: "年龄", + dataIndex: 'patientage', + width:100, + minimumWidth:80, + maximumWidth:330 + }, + { + title: "电话", + dataIndex: 'patienttel', + width:200, + minimumWidth:100, + maximumWidth:300, + editMultiline: true + }, + { + title: "身份证号", + dataIndex: 'patientidenno', + width:120, + minimumWidth:100, + maximumWidth:250 + }, + { + title: "检测项目", + dataIndex: 'hisitemnamelist', + width:120, + minimumWidth:100, + maximumWidth:250 + }, + { + title: "开单科室", + dataIndex: 'deptname', + width:150, + minimumWidth:100, + maximumWidth:250 + }, + { + title: "开单医生", + dataIndex: 'doctorname', + width:140, + minimumWidth:100, + maximumWidth:250 + } + , + { + title: "接收时间", + dataIndex: 'incepttime', + width:120, + minimumWidth:100, + maximumWidth:250 + }, + { + title: "核收时间", + dataIndex: 'accepttime', + width:220, + minimumWidth:100, + maximumWidth:250 + } + ] + model: ListModel{ + id: customModel + } + } + Component.onCompleted: { + // generateTestData(1000) // 生成100条测试数据 + for (var i = 0; i < 100000; i++) { + customModel.append(generateTestData(i)) + } + // uvRecord.setProperty() + } + Component{ + id:com_avatar + Item{ + anchors.fill: parent + FluClip{ + anchors.centerIn: parent + width: Math.min(parent.width,parent.height) + height: width + radius: [width/2,width/2,width/2,width/2] + Image{ + anchors.fill: parent + source: display + sourceSize: Qt.size(80,80) + fillMode: Image.PreserveAspectFit + } + } + } + } + Component { + id: com_checkbox + Item{ + FluCheckBox { + width: 15 + height: 15 + anchors.centerIn: parent + } + } + } + Component { + id: com_header_checkbox + Item{ + FluCheckBox { + width: 15 + height: 15 + anchors.centerIn: parent + } + } + } + Component{ + id: com_action + Item{ + Row{ + anchors.centerIn: parent + FluTextButton{ + text:"插入" + onClicked: { + // uvRecord.insertRecord(row) + uvRecord.insert(row,generateTestData(row)) + } + } + FluTextButton{ + text:"上移" + onClicked:{ + if (row > 0) { + uvRecord.move(row, row - 1, 1) + } + } + } + FluTextButton{ + text:"下移" + onClicked:{ + if (row < uvRecord.rowCount() - 1) { + uvRecord.move(row, row + 2, 1) // 注意这里是 row + 2 + } + } + } + FluTextButton{ + text:"查看" + onClicked: { + showSuccess(JSON.stringify(control.model.get(row))) + } + } + FluTextButton{ + text:"删除" + onClicked: { + uvRecord.remove(row) + } + } + } + } + } + + function generateTestData(i) { + var sexes = ["男", "女"] + var departments = ["内科", "外科", "儿科", "妇科", "眼科"] + var doctors = ["张医生", "李医生", "王医生", "刘医生", "陈医生"] + var images = [ + "qrc:/res/image/pages/exchange.png", + "qrc:/res/image/pages/nuclear.png", + "qrc:/res/image/pages/ocr.png", + "qrc:/res/image/pages/room-temperature.png", + "qrc:/res/image/pages/rt-pcr-machine.png" + ] + return { + testdate: new Date().toLocaleDateString(), + barcode: "BC" + (1000000 + i).toString(), + sampleid: "S" + (100000 + i).toString(), + patientname: "患者" + (i + 1), + patientsex: sexes[Math.floor(Math.random() * sexes.length)], + patientage: Math.floor(Math.random() * 80 + 1) + "岁", + patienttel: "1" + Math.floor(Math.random() * 9000000000 + 1000000000), + patientidenno: (310000000000000000 + i).toString(), + hisitemnamelist: "项目1,项目2,项目3", + deptname: departments[Math.floor(Math.random() * departments.length)], + doctorname: doctors[Math.floor(Math.random() * doctors.length)], + incepttime: new Date().toLocaleString(), + accepttime: new Date().toLocaleString(), + imageurl: images[Math.floor(Math.random() * images.length)], + _minimumHeight: 42, + _maximumHeight: 800, + height: 42 + } + + } + +} diff --git a/example/qml/page/T_Theme.qml b/example/qml/page/T_Theme.qml index c1e5f280..9a01f989 100644 --- a/example/qml/page/T_Theme.qml +++ b/example/qml/page/T_Theme.qml @@ -13,7 +13,7 @@ FluScrollablePage{ FluFrame{ Layout.fillWidth: true - Layout.preferredHeight: 408 + Layout.fillHeight: true padding: 10 ColumnLayout{ @@ -124,12 +124,69 @@ FluScrollablePage{ Layout.topMargin: 20 } FluToggleSwitch{ + id: toggle_blur Layout.topMargin: 5 checked: FluTheme.blurBehindWindowEnabled onClicked: { FluTheme.blurBehindWindowEnabled = !FluTheme.blurBehindWindowEnabled } } + FluText{ + visible: FluTheme.blurBehindWindowEnabled || window.effect === "dwm-blur" + text: qsTr("window tintOpacity") + Layout.topMargin: 20 + } + FluSlider{ + visible: FluTheme.blurBehindWindowEnabled || window.effect === "dwm-blur" + Layout.topMargin: 5 + to:1 + stepSize:0.1 + onValueChanged: { + window.tintOpacity = value + } + Component.onCompleted: { + value = window.tintOpacity + } + } + FluText{ + visible: FluTheme.blurBehindWindowEnabled + text: qsTr("window blurRadius") + Layout.topMargin: 20 + } + FluSlider{ + visible: FluTheme.blurBehindWindowEnabled + Layout.topMargin: 5 + to:100 + stepSize:1 + onValueChanged: { + window.blurRadius = value + } + Component.onCompleted: { + value = window.blurRadius + } + } + FluText{ + text: qsTr("window effect") + Layout.topMargin: 20 + } + Row{ + spacing: 10 + Repeater{ + model: window.availableEffects + delegate: FluRadioButton{ + checked: window.effect === modelData + text: qsTr(`${modelData}`) + clickListener:function(){ + window.effect = modelData + if(window.effective){ + FluTheme.blurBehindWindowEnabled = false + toggle_blur.checked = Qt.binding( function() {return FluTheme.blurBehindWindowEnabled}) + } + } + } + + } + } } } CodeExpander{ diff --git a/example/qml/window/MainWindow.qml b/example/qml/window/MainWindow.qml index 610f1f3f..7f3e0fbb 100644 --- a/example/qml/window/MainWindow.qml +++ b/example/qml/window/MainWindow.qml @@ -236,6 +236,7 @@ FluWindow { id: reveal target: window.containerItem() anchors.fill: parent + darkToLight: FluTheme.dark onAnimationFinished:{ //动画结束后释放资源 loader_reveal.sourceComponent = undefined @@ -256,17 +257,14 @@ FluWindow { } function handleDarkChanged(button){ - if(!FluTheme.animationEnabled || window.fitsAppBarWindows === false){ + if(FluTools.isMacos() || !FluTheme.animationEnabled){ changeDark() }else{ - if(loader_reveal.sourceComponent){ - return - } loader_reveal.sourceComponent = com_reveal var target = window.containerItem() var pos = button.mapToItem(target,0,0) - var mouseX = pos.x - var mouseY = pos.y + var mouseX = pos.x + button.width / 2 + var mouseY = pos.y + button.height / 2 var radius = Math.max(distance(mouseX,mouseY,0,0),distance(mouseX,mouseY,target.width,0),distance(mouseX,mouseY,0,target.height),distance(mouseX,mouseY,target.width,target.height)) var reveal = loader_reveal.item reveal.start(reveal.width*Screen.devicePixelRatio,reveal.height*Screen.devicePixelRatio,Qt.point(mouseX,mouseY),radius) diff --git a/example/src/component/CircularReveal.cpp b/example/src/component/CircularReveal.cpp index 65de3f88..d0d652e3 100644 --- a/example/src/component/CircularReveal.cpp +++ b/example/src/component/CircularReveal.cpp @@ -25,13 +25,32 @@ void CircularReveal::paint(QPainter *painter) { path.moveTo(_center.x(), _center.y()); path.addEllipse(QPointF(_center.x(), _center.y()), _radius, _radius); painter->setCompositionMode(QPainter::CompositionMode_Clear); - painter->fillPath(path, Qt::black); + if(_darkToLight){ + painter->fillPath(path, Qt::white); + }else{ + QPainterPath outerRect; + outerRect.addRect(0, 0, width(), height()); + outerRect = outerRect.subtracted(path); + painter->fillPath(outerRect, Qt::black); + } painter->restore(); } [[maybe_unused]] void CircularReveal::start(int w, int h, const QPoint ¢er, int radius) { - _anim->setStartValue(0); - _anim->setEndValue(radius); + if (_anim->state() == QAbstractAnimation::Running) { + _anim->stop(); + int currentRadius = _radius; + _anim->setStartValue(currentRadius); + _anim->setEndValue(_darkToLight ? 0 : radius); + } else { + if(_darkToLight){ + _anim->setStartValue(radius); + _anim->setEndValue(0); + }else{ + _anim->setStartValue(0); + _anim->setEndValue(radius); + } + } _center = center; _grabResult = _target->grabToImage(QSize(w, h)); connect(_grabResult.data(), &QQuickItemGrabResult::ready, this, diff --git a/example/src/component/CircularReveal.h b/example/src/component/CircularReveal.h index 62db7c97..2c1adde2 100644 --- a/example/src/component/CircularReveal.h +++ b/example/src/component/CircularReveal.h @@ -10,6 +10,7 @@ class CircularReveal : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY_AUTO_P(QQuickItem *, target) Q_PROPERTY_AUTO(int, radius) + Q_PROPERTY_AUTO(bool, darkToLight) public: explicit CircularReveal(QQuickItem *parent = nullptr); void paint(QPainter *painter) override; diff --git a/src/FluFrameless.cpp b/src/FluFrameless.cpp index 94ef70d3..76a75f37 100644 --- a/src/FluFrameless.cpp +++ b/src/FluFrameless.cpp @@ -1,4 +1,5 @@ #include "FluFrameless.h" +#include "FluTheme.h" #include #include @@ -8,12 +9,69 @@ #ifdef Q_OS_WIN -#pragma comment (lib, "user32.lib") -#pragma comment (lib, "dwmapi.lib") +static RTL_OSVERSIONINFOW GetRealOSVersionImpl() { + HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); + using RtlGetVersionPtr = NTSTATUS(WINAPI *)(PRTL_OSVERSIONINFOW); + auto pRtlGetVersion = + reinterpret_cast(::GetProcAddress(hMod, "RtlGetVersion")); + RTL_OSVERSIONINFOW rovi{}; + rovi.dwOSVersionInfoSize = sizeof(rovi); + pRtlGetVersion(&rovi); + return rovi; +} -#include -#include -#include +RTL_OSVERSIONINFOW GetRealOSVersion() { + static const auto result = GetRealOSVersionImpl(); + return result; +} + +static inline bool isWin8OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 6) || (rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 2); +} + +static inline bool isWin8Point1OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 6) || (rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 3); +} + +static inline bool isWin10OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0); +} + +static inline bool isWin101809OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || + (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 17763); +} + +static inline bool isWin101903OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || + (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 18362); +} + +static inline bool isWin11OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || + (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22000); +} + +static inline bool isWin1122H2OrGreater() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || + (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22621); +} + +static inline bool isWin10Only() { + return isWin10OrGreater() && !isWin11OrGreater(); +} + +static inline bool isWin7Only() { + RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); + return rovi.dwMajorVersion = 7; +} static inline QByteArray qtNativeEventType() { static const auto result = "windows_generic_MSG"; @@ -48,6 +106,138 @@ static inline void setShadow(HWND hwnd) { } } +static inline bool setWindowDarkMode(HWND hwnd, const BOOL enable) { + return bool(DwmSetWindowAttribute(hwnd, DWMWINDOWATTRIBUTE::DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable, sizeof(BOOL))); +} + +static inline bool setWindowEffect(HWND hwnd, const QString &key, const bool &enable) { + static constexpr const MARGINS extendedMargins = {-1, -1, -1, -1}; + if (key == QStringLiteral("mica")) { + if (!isWin11OrGreater()) { + return false; + } + if (enable) { + DwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + if (isWin1122H2OrGreater()) { + const DWM_SYSTEMBACKDROP_TYPE backdropType = DWMSBT_MAINWINDOW; + DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + } else { + const BOOL enable = TRUE; + DwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable)); + } + } else { + if (isWin1122H2OrGreater()) { + const DWM_SYSTEMBACKDROP_TYPE backdropType = DWMSBT_AUTO; + DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + } else { + const BOOL enable = FALSE; + DwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable)); + } + } + BOOL isDark = FluTheme::getInstance()->dark(); + setWindowDarkMode(hwnd, isDark); + return true; + } + + if (key == QStringLiteral("mica-alt")) { + if (!isWin1122H2OrGreater()) { + return false; + } + if (enable) { + DwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + const DWM_SYSTEMBACKDROP_TYPE backdropType = DWMSBT_TABBEDWINDOW; + DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + } else { + const DWM_SYSTEMBACKDROP_TYPE backdropType = DWMSBT_AUTO; + DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + } + BOOL isDark = FluTheme::getInstance()->dark(); + setWindowDarkMode(hwnd, isDark); + return true; + } + + if (key == QStringLiteral("acrylic")) { + if (!isWin11OrGreater()) { + return false; + } + if (enable) { + MARGINS margins{-1, -1, -1, -1}; + DwmExtendFrameIntoClientArea(hwnd, &margins); + DWM_SYSTEMBACKDROP_TYPE system_backdrop_type = + DWM_SYSTEMBACKDROP_TYPE::DWMSBT_TRANSIENTWINDOW; + DwmSetWindowAttribute(hwnd, DWMWINDOWATTRIBUTE::DWMWA_SYSTEMBACKDROP_TYPE, + &system_backdrop_type, sizeof(DWM_SYSTEMBACKDROP_TYPE)); + } else { + const DWM_SYSTEMBACKDROP_TYPE backdropType = DWMSBT_AUTO; + DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + } + BOOL isDark = FluTheme::getInstance()->dark(); + setWindowDarkMode(hwnd, isDark); + return true; + } + + if (key == QStringLiteral("dwm-blur")) { + if (isWin7Only() && !isCompositionEnabled()) { + return false; + } + BOOL isDark = FluTheme::getInstance()->dark(); + setWindowDarkMode(hwnd, isDark && enable); + typedef HRESULT(WINAPI * SetWindowCompositionAttributePtr)(HWND, + PWINDOWCOMPOSITIONATTRIBDATA); + SetWindowCompositionAttributePtr SetWindowCompositionAttribute; + if (isWin8OrGreater()) { + HMODULE module = LoadLibraryW(L"user32.dll"); + SetWindowCompositionAttribute = reinterpret_cast( + GetProcAddress(module, "SetWindowCompositionAttribute")); + if (!SetWindowCompositionAttribute) { + return false; + } + } + if (enable) { + 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); + SetWindowCompositionAttribute(hwnd, &wcad); + } else { + DWM_BLURBEHIND bb{}; + bb.fEnable = TRUE; + bb.dwFlags = DWM_BB_ENABLE; + DwmEnableBlurBehindWindow(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); + SetWindowCompositionAttribute(hwnd, &wcad); + } else { + DWM_BLURBEHIND bb{}; + bb.fEnable = FALSE; + bb.dwFlags = DWM_BB_ENABLE; + DwmEnableBlurBehindWindow(hwnd, &bb); + } + } + return true; + } + + return false; +} + #endif bool containsCursorToItem(QQuickItem *item) { @@ -70,6 +260,7 @@ FluFrameless::FluFrameless(QQuickItem *parent) : QQuickItem{parent} { _closeButton = nullptr; _topmost = false; _disabled = false; + _effect = "normal"; _isWindows11OrGreater = FluTools::getInstance()->isWindows11OrGreater(); } @@ -107,6 +298,9 @@ void FluFrameless::componentComplete() { #endif HWND hwnd = reinterpret_cast(window()->winId()); DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE); +# if (QT_VERSION == QT_VERSION_CHECK(6, 7, 2)) + style &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); +# endif if (_fixSize) { ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION);; for (int i = 0; i <= QGuiApplication::screens().count() - 1; ++i) { @@ -125,6 +319,42 @@ void FluFrameless::componentComplete() { if (!window()->property("_hideShadow").toBool()) { setShadow(hwnd); } + if (isWin11OrGreater()) { + availableEffects({"mica", "mica-alt", "acrylic", "dwm-blur", "normal"}); + } else if (isWin7Only()) { + availableEffects({"dwm-blur","normal"}); + } + if (!_effect.isEmpty()) { + effective(setWindowEffect(hwnd, _effect, true)); + if (effective()) { + _currentEffect = effect(); + } + } + connect(this, &FluFrameless::effectChanged, this, [hwnd, this] { + if (effect() == _currentEffect) { + return; + } + if (effective()) { + setWindowEffect(hwnd, _currentEffect, false); + } + effective(setWindowEffect(hwnd, effect(), true)); + if (effective()) { + _currentEffect = effect(); + } else { + _effect = "normal"; + _currentEffect = "normal"; + } + }); + connect(FluTheme::getInstance(), &FluTheme::blurBehindWindowEnabledChanged, this, [this] { + if (FluTheme::getInstance()->blurBehindWindowEnabled()) { + effect("normal"); + } + }); + connect(FluTheme::getInstance(), &FluTheme::darkChanged, this, [hwnd, this] { + if (effective() && !_currentEffect.isEmpty() && _currentEffect != "normal") { + setWindowDarkMode(hwnd, FluTheme::getInstance()->dark()); + } + }); #endif auto appBarHeight = _appbar->height(); h = qRound(h + appBarHeight); @@ -234,6 +464,9 @@ void FluFrameless::componentComplete() { *result = FALSE; return false; } else if (uMsg == WM_NCACTIVATE) { + if (effective() || (!effect().isEmpty() && _currentEffect!="normal")) { + return false; + } *result = TRUE; return true; } else if (_isWindows11OrGreater && (uMsg == WM_NCLBUTTONDBLCLK || uMsg == WM_NCLBUTTONDOWN)) { diff --git a/src/FluFrameless.h b/src/FluFrameless.h index 332fb33d..db095210 100644 --- a/src/FluFrameless.h +++ b/src/FluFrameless.h @@ -6,6 +6,82 @@ #include #include "stdafx.h" +#ifdef Q_OS_WIN + +#pragma comment (lib, "user32.lib") +#pragma comment (lib, "dwmapi.lib") + +#include +#include +#include + +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 *; + + +#endif + #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) using QT_NATIVE_EVENT_RESULT_TYPE = qintptr; using QT_ENTER_EVENT_TYPE = QEnterEvent; @@ -23,6 +99,9 @@ class FluFrameless : public QQuickItem, QAbstractNativeEventFilter { Q_PROPERTY_AUTO(bool, topmost) Q_PROPERTY_AUTO(bool, disabled) Q_PROPERTY_AUTO(bool, fixSize) + Q_PROPERTY_AUTO(QString, effect) + Q_PROPERTY_READONLY_AUTO(bool, effective) + Q_PROPERTY_READONLY_AUTO(QStringList, availableEffects) QML_NAMED_ELEMENT(FluFrameless) public: explicit FluFrameless(QQuickItem *parent = nullptr); @@ -75,4 +154,5 @@ private: quint64 _clickTimer = 0; bool _isWindows11OrGreater = false; QList> _hitTestList; + QString _currentEffect; }; diff --git a/src/Qt5/imports/FluentUI/Controls/FluWindow.qml b/src/Qt5/imports/FluentUI/Controls/FluWindow.qml index ae9e7f4f..9ab23dab 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluWindow.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluWindow.qml @@ -13,6 +13,11 @@ Window { property bool fixSize: false property Component loadingItem: com_loading property bool fitsAppBarWindows: false + property var tintOpacity: FluTheme.dark ? 0.80 : 0.75 + property int blurRadius: 60 + property alias effect: frameless.effect + readonly property alias effective: frameless.effective + readonly property var availableEffects: frameless.availableEffects property Item appBar: FluAppBar { title: window.title height: 30 @@ -24,6 +29,15 @@ Window { icon: window.windowIcon } property color backgroundColor: { + if(frameless.effective && active){ + var backcolor + if(frameless.effect==="dwm-blur"){ + backcolor = FluTools.withOpacity(FluTheme.windowActiveBackgroundColor, window.tintOpacity) + }else{ + backcolor = "transparent" + } + return backcolor + } if(active){ return FluTheme.windowActiveBackgroundColor } @@ -107,6 +121,11 @@ Window { Component.onDestruction: { frameless.onDestruction() } + onEffectiveChanged: { + if(effective){ + FluTheme.blurBehindWindowEnabled = false + } + } } Component{ id:com_background @@ -162,8 +181,8 @@ Window { FluAcrylic{ anchors.fill: parent target: img_back - tintOpacity: FluTheme.dark ? 0.80 : 0.75 - blurRadius: 64 + tintOpacity: window.tintOpacity + blurRadius: window.blurRadius visible: window.active && FluTheme.blurBehindWindowEnabled tintColor: FluTheme.dark ? Qt.rgba(0, 0, 0, 1) : Qt.rgba(1, 1, 1, 1) targetRect: Qt.rect(window.x-window.screen.virtualX,window.y-window.screen.virtualY,window.width,window.height) diff --git a/src/Qt5/imports/FluentUI/plugins.qmltypes b/src/Qt5/imports/FluentUI/plugins.qmltypes index 66365ab0..5390b832 100644 --- a/src/Qt5/imports/FluentUI/plugins.qmltypes +++ b/src/Qt5/imports/FluentUI/plugins.qmltypes @@ -150,6 +150,9 @@ Module { Property { name: "topmost"; type: "bool" } Property { name: "disabled"; type: "bool" } Property { name: "fixSize"; type: "bool" } + Property { name: "effect"; type: "string" } + Property { name: "effective"; type: "bool" } + Property {name: "availableEffects"; type: "QVariant"} Method { name: "showFullScreen" } Method { name: "showMaximized" } Method { name: "showMinimized" } @@ -396,6 +399,7 @@ Module { Property { name: "nativeText"; type: "bool" } Property { name: "animationEnabled"; type: "bool" } Property { name: "blurBehindWindowEnabled"; type: "bool" } + Property { name: "micaBackgroundColor"; type: "QColor" } } Component { name: "FluThemeType" @@ -4150,6 +4154,12 @@ Module { Property { name: "autoVisible"; type: "bool" } Property { name: "autoCenter"; type: "bool" } Property { name: "autoDestroy"; type: "bool" } + + Property { name: "effect"; type: "string" } + Property { name: "effective"; type: "bool" } + Property { name: "blurRadius"; type: "int" } + Property { name: "tintOpacity"; type: "QVariant" } + Property { name: "useSystemAppBar"; type: "bool" } Property { name: "resizeBorderColor"; type: "QColor" } Property { name: "resizeBorderWidth"; type: "int" }