add basic.

This commit is contained in:
amass 2024-08-31 04:13:21 +08:00
parent 7f5f273e14
commit 8b6bce4e3d
48 changed files with 3029 additions and 632 deletions

View File

@ -2,10 +2,12 @@
#include "Rectangle.h" #include "Rectangle.h"
#include <QGuiApplication> #include <QGuiApplication>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQuickStyle>
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
LOG(info) << "app start..."; LOG(info) << "app start...";
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
QQuickStyle::setStyle("Basic");
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
QObject::connect( QObject::connect(
&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); },

4
Fluent/AccentColor.cpp Normal file
View File

@ -0,0 +1,4 @@
#include "AccentColor.h"
AccentColor::AccentColor(QObject *parent) : QObject{parent} {
}

22
Fluent/AccentColor.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef __ACCENTCOLOR_H__
#define __ACCENTCOLOR_H__
#include "Utilities.h"
#include <QObject>
class AccentColor : public QObject {
Q_OBJECT
Q_PROPERTY_AUTO(QColor, darkest)
Q_PROPERTY_AUTO(QColor, darker)
Q_PROPERTY_AUTO(QColor, dark)
Q_PROPERTY_AUTO(QColor, normal)
Q_PROPERTY_AUTO(QColor, light)
Q_PROPERTY_AUTO(QColor, lighter)
Q_PROPERTY_AUTO(QColor, lightest)
QML_ELEMENT
public:
AccentColor(QObject *parent = nullptr);
};
#endif // __ACCENTCOLOR_H__

View File

@ -1,5 +1,5 @@
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Gui Quick) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Gui Quick QuickControls2)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Quick) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Quick QuickControls2)
qt_standard_project_setup(REQUIRES 6.5) qt_standard_project_setup(REQUIRES 6.5)
@ -16,25 +16,41 @@ qt6_add_qml_module(Fluent
URI Fluent URI Fluent
VERSION 1.0 VERSION 1.0
SOURCES SOURCES
AccentColor.h AccentColor.cpp
App.h App.cpp App.h App.cpp
CircularReveal.h CircularReveal.cpp
Colors.h Colors.cpp
Frameless.h Frameless.cpp Frameless.h Frameless.cpp
Icons.h Icons.h
Rectangle.h Rectangle.cpp Rectangle.h Rectangle.cpp
TextStyle.h TextStyle.cpp
Theme.h Theme.cpp Theme.h Theme.cpp
Utilities.h Utilities.cpp Utilities.h Utilities.cpp
QML_FILES QML_FILES
qml/Acrylic.qml qml/Acrylic.qml
qml/AppBar.qml qml/AppBar.qml
qml/Button.qml
qml/ContentDialog.qml
qml/ControlBackground.qml
qml/FilledButton.qml
qml/FocusRectangle.qml
qml/Icon.qml qml/Icon.qml
qml/IconButton.qml qml/IconButton.qml
qml/ImageButton.qml
qml/InfoBar.qml qml/InfoBar.qml
qml/Loader.qml
qml/Object.qml qml/Object.qml
qml/Popup.qml
qml/ProgressRing.qml
qml/Router.qml qml/Router.qml
qml/ScrollBar.qml
qml/Shadow.qml qml/Shadow.qml
qml/Text.qml qml/Text.qml
qml/Tooltip.qml
qml/Window.qml qml/Window.qml
RESOURCES RESOURCES
resources/noise.png resources/noise.png
resources/FluentIcons.ttf
) )
target_include_directories(Fluent target_include_directories(Fluent
@ -44,4 +60,5 @@ target_include_directories(Fluent
target_link_libraries(Fluent target_link_libraries(Fluent
PUBLIC Qt${QT_VERSION_MAJOR}::Gui PUBLIC Qt${QT_VERSION_MAJOR}::Gui
PRIVATE Qt${QT_VERSION_MAJOR}::Quick PRIVATE Qt${QT_VERSION_MAJOR}::Quick
INTERFACE Qt${QT_VERSION_MAJOR}::QuickControls2
) )

66
Fluent/CircularReveal.cpp Normal file
View File

@ -0,0 +1,66 @@
#include "CircularReveal.h"
#include <QGuiApplication>
#include <QQuickItemGrabResult>
#include <QPainterPath>
CircularReveal::CircularReveal(QQuickItem *parent) : QQuickPaintedItem(parent) {
m_target = nullptr;
m_radius = 0;
_anim = new QPropertyAnimation(this, "radius", this);
_anim->setDuration(333);
_anim->setEasingCurve(QEasingCurve::OutCubic);
setVisible(false);
connect(_anim, &QPropertyAnimation::finished, this, [=]() {
update();
setVisible(false);
Q_EMIT animationFinished();
});
connect(this, &CircularReveal::radiusChanged, this, [=]() { update(); });
}
void CircularReveal::paint(QPainter *painter) {
painter->save();
painter->drawImage(QRect(0, 0, static_cast<int>(width()), static_cast<int>(height())), _source);
QPainterPath path;
path.moveTo(_center.x(), _center.y());
path.addEllipse(QPointF(_center.x(), _center.y()), m_radius, m_radius);
painter->setCompositionMode(QPainter::CompositionMode_Clear);
if (m_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 &center, int radius) {
if (_anim->state() == QAbstractAnimation::Running) {
_anim->stop();
int currentRadius = m_radius;
_anim->setStartValue(currentRadius);
_anim->setEndValue(m_darkToLight ? 0 : radius);
} else {
if (m_darkToLight) {
_anim->setStartValue(radius);
_anim->setEndValue(0);
} else {
_anim->setStartValue(0);
_anim->setEndValue(radius);
}
}
_center = center;
_grabResult = m_target->grabToImage(QSize(w, h));
connect(_grabResult.data(), &QQuickItemGrabResult::ready, this,
&CircularReveal::handleGrabResult);
}
void CircularReveal::handleGrabResult() {
_grabResult.data()->image().swap(_source);
update();
setVisible(true);
Q_EMIT imageChanged();
_anim->start();
}

31
Fluent/CircularReveal.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef __CIRCULARREVEAL_H__
#define __CIRCULARREVEAL_H__
#include "Utilities.h"
#include <QPainter>
#include <QPropertyAnimation>
#include <QQuickItem>
#include <QQuickPaintedItem>
class CircularReveal : public QQuickPaintedItem {
Q_OBJECT
QML_ELEMENT
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;
Q_INVOKABLE void start(int w, int h, const QPoint &center, int radius);
Q_SIGNAL void imageChanged();
Q_SIGNAL void animationFinished();
Q_SLOT void handleGrabResult();
private:
QPropertyAnimation *_anim = nullptr;
QImage _source;
QPoint _center;
QSharedPointer<QQuickItemGrabResult> _grabResult;
};
#endif // __CIRCULARREVEAL_H__

135
Fluent/Colors.cpp Normal file
View File

@ -0,0 +1,135 @@
#include "Colors.h"
Colors *Colors::instance() {
static Colors *self = nullptr;
if (self == nullptr) {
self = new Colors();
}
return self;
}
Colors *Colors::create(QQmlEngine *, QJSEngine *) {
auto ret = instance();
QJSEngine::setObjectOwnership(ret, QJSEngine::CppOwnership);
return ret;
}
Colors::Colors(QObject *parent) : QObject{parent} {
m_Transparent = QColor(0, 0, 0, 0);
m_Black = QColor(0, 0, 0);
m_White = QColor(255, 255, 255);
m_Grey10 = QColor(250, 249, 248);
m_Grey20 = QColor(243, 242, 241);
m_Grey30 = QColor(237, 235, 233);
m_Grey40 = QColor(225, 223, 221);
m_Grey50 = QColor(210, 208, 206);
m_Grey60 = QColor(200, 198, 196);
m_Grey70 = QColor(190, 185, 184);
m_Grey80 = QColor(179, 176, 173);
m_Grey90 = QColor(161, 159, 157);
m_Grey100 = QColor(151, 149, 146);
m_Grey110 = QColor(138, 136, 134);
m_Grey120 = QColor(121, 119, 117);
m_Grey130 = QColor(96, 94, 92);
m_Grey140 = QColor(72, 70, 68);
m_Grey150 = QColor(59, 58, 57);
m_Grey160 = QColor(50, 49, 48);
m_Grey170 = QColor(41, 40, 39);
m_Grey180 = QColor(37, 36, 35);
m_Grey190 = QColor(32, 31, 30);
m_Grey200 = QColor(27, 26, 25);
m_Grey210 = QColor(22, 21, 20);
m_Grey220 = QColor(17, 16, 15);
auto yellow = new AccentColor(this);
yellow->darkest(QColor(249, 168, 37));
yellow->darker(QColor(251, 192, 45));
yellow->dark(QColor(253, 212, 53));
yellow->normal(QColor(255, 235, 59));
yellow->light(QColor(255, 238, 88));
yellow->lighter(QColor(255, 241, 118));
yellow->lightest(QColor(255, 245, 155));
m_Yellow = yellow;
auto orange = new AccentColor(this);
orange->darkest(QColor(153, 61, 7));
orange->darker(QColor(172, 68, 8));
orange->dark(QColor(209, 88, 10));
orange->normal(QColor(247, 99, 12));
orange->light(QColor(248, 122, 48));
orange->lighter(QColor(249, 145, 84));
orange->lightest(QColor(250, 192, 106));
m_Orange = orange;
auto red = new AccentColor(this);
red->darkest(QColor(143, 10, 21));
red->darker(QColor(162, 11, 24));
red->dark(QColor(185, 13, 28));
red->normal(QColor(232, 17, 35));
red->light(QColor(236, 64, 79));
red->lighter(QColor(238, 88, 101));
red->lightest(QColor(240, 107, 118));
m_Red = red;
auto magenta = new AccentColor(this);
magenta->darkest(QColor(111, 0, 79));
magenta->darker(QColor(160, 7, 108));
magenta->dark(QColor(181, 13, 125));
magenta->normal(QColor(227, 0, 140));
magenta->light(QColor(234, 77, 168));
magenta->lighter(QColor(238, 110, 193));
magenta->lightest(QColor(241, 140, 213));
m_Magenta = magenta;
auto purple = new AccentColor(this);
purple->darkest(QColor(44, 15, 118));
purple->darker(QColor(61, 15, 153));
purple->dark(QColor(78, 17, 174));
purple->normal(QColor(104, 33, 122));
purple->light(QColor(123, 76, 157));
purple->lighter(QColor(141, 110, 189));
purple->lightest(QColor(158, 142, 217));
m_Purple = purple;
auto blue = new AccentColor(this);
blue->darkest(QColor(0, 74, 131));
blue->darker(QColor(0, 84, 148));
blue->dark(QColor(0, 102, 180));
blue->normal(QColor(0, 120, 212));
blue->light(QColor(38, 140, 220));
blue->lighter(QColor(76, 160, 224));
blue->lightest(QColor(96, 171, 228));
m_Blue = blue;
auto teal = new AccentColor(this);
teal->darkest(QColor(0, 110, 91));
teal->darker(QColor(0, 124, 103));
teal->dark(QColor(0, 151, 125));
teal->normal(QColor(0, 178, 148));
teal->light(QColor(38, 189, 164));
teal->lighter(QColor(77, 201, 180));
teal->lightest(QColor(96, 207, 188));
m_Teal = teal;
auto green = new AccentColor(this);
green->darkest(QColor(9, 76, 9));
green->darker(QColor(12, 93, 12));
green->dark(QColor(14, 111, 14));
green->normal(QColor(16, 124, 16));
green->light(QColor(39, 137, 57));
green->lighter(QColor(76, 156, 76));
green->lightest(QColor(106, 173, 106));
m_Green = green;
}
AccentColor *Colors::createAccentColor(const QColor &primaryColor) {
auto accentColor = new AccentColor(this);
accentColor->normal(primaryColor);
accentColor->dark(Utilities::instance()->withOpacity(primaryColor, 0.9));
accentColor->light(Utilities::instance()->withOpacity(primaryColor, 0.9));
accentColor->darker(Utilities::instance()->withOpacity(accentColor->dark(), 0.8));
accentColor->lighter(Utilities::instance()->withOpacity(accentColor->light(), 0.8));
accentColor->darkest(Utilities::instance()->withOpacity(accentColor->darker(), 0.7));
accentColor->lightest(Utilities::instance()->withOpacity(accentColor->lighter(), 0.7));
return accentColor;
}

56
Fluent/Colors.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef __COLORS_H__
#define __COLORS_H__
#include "AccentColor.h"
#include "Utilities.h"
#include <QObject>
class Colors : public QObject {
Q_OBJECT
Q_PROPERTY_AUTO(QColor, Transparent)
Q_PROPERTY_AUTO(QColor, Black)
Q_PROPERTY_AUTO(QColor, White)
Q_PROPERTY_AUTO(QColor, Grey10)
Q_PROPERTY_AUTO(QColor, Grey20)
Q_PROPERTY_AUTO(QColor, Grey30)
Q_PROPERTY_AUTO(QColor, Grey40)
Q_PROPERTY_AUTO(QColor, Grey50)
Q_PROPERTY_AUTO(QColor, Grey60)
Q_PROPERTY_AUTO(QColor, Grey70)
Q_PROPERTY_AUTO(QColor, Grey80)
Q_PROPERTY_AUTO(QColor, Grey90)
Q_PROPERTY_AUTO(QColor, Grey100)
Q_PROPERTY_AUTO(QColor, Grey110)
Q_PROPERTY_AUTO(QColor, Grey120)
Q_PROPERTY_AUTO(QColor, Grey130)
Q_PROPERTY_AUTO(QColor, Grey140)
Q_PROPERTY_AUTO(QColor, Grey150)
Q_PROPERTY_AUTO(QColor, Grey160)
Q_PROPERTY_AUTO(QColor, Grey170)
Q_PROPERTY_AUTO(QColor, Grey180)
Q_PROPERTY_AUTO(QColor, Grey190)
Q_PROPERTY_AUTO(QColor, Grey200)
Q_PROPERTY_AUTO(QColor, Grey210)
Q_PROPERTY_AUTO(QColor, Grey220)
Q_PROPERTY_AUTO_P(AccentColor *, Yellow)
Q_PROPERTY_AUTO_P(AccentColor *, Orange)
Q_PROPERTY_AUTO_P(AccentColor *, Red)
Q_PROPERTY_AUTO_P(AccentColor *, Magenta)
Q_PROPERTY_AUTO_P(AccentColor *, Purple)
Q_PROPERTY_AUTO_P(AccentColor *, Blue)
Q_PROPERTY_AUTO_P(AccentColor *, Teal)
Q_PROPERTY_AUTO_P(AccentColor *, Green)
QML_ELEMENT
QML_SINGLETON
public:
static Colors *instance();
static Colors *create(QQmlEngine *, QJSEngine *);
Q_INVOKABLE AccentColor *createAccentColor(const QColor &primaryColor);
protected:
Colors(QObject *parent = nullptr);
};
#endif // __COLORS_H__

View File

@ -1,4 +1,5 @@
#include "Frameless.h" #include "Frameless.h"
#include "Theme.h"
#include "Utilities.h" #include "Utilities.h"
#include <QQuickWindow> #include <QQuickWindow>
#include <dwmapi.h> #include <dwmapi.h>
@ -19,6 +20,121 @@ static inline void setShadow(HWND hwnd) {
} }
} }
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 _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 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 WINDOWCOMPOSITIONATTRIBDATA {
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
};
using PWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA *;
struct ACCENT_POLICY {
DWORD dwAccentState;
DWORD dwAccentFlags;
DWORD dwGradientColor; // #AABBGGRR
DWORD dwAnimationId;
};
using PACCENT_POLICY = ACCENT_POLICY *;
typedef HRESULT(WINAPI *DwmSetWindowAttributeFunc)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute,
DWORD cbAttribute);
typedef HRESULT(WINAPI *DwmExtendFrameIntoClientAreaFunc)(HWND hwnd, const MARGINS *pMarInset);
typedef HRESULT(WINAPI *DwmIsCompositionEnabledFunc)(BOOL *pfEnabled);
typedef HRESULT(WINAPI *DwmEnableBlurBehindWindowFunc)(HWND hWnd, const DWM_BLURBEHIND *pBlurBehind);
typedef BOOL(WINAPI *SetWindowCompositionAttributeFunc)(HWND hwnd, const WINDOWCOMPOSITIONATTRIBDATA *);
static DwmSetWindowAttributeFunc pDwmSetWindowAttribute = nullptr;
static DwmExtendFrameIntoClientAreaFunc pDwmExtendFrameIntoClientArea = nullptr;
static DwmIsCompositionEnabledFunc pDwmIsCompositionEnabled = nullptr;
static DwmEnableBlurBehindWindowFunc pDwmEnableBlurBehindWindow = nullptr;
static SetWindowCompositionAttributeFunc pSetWindowCompositionAttribute = nullptr;
static RTL_OSVERSIONINFOW GetRealOSVersionImpl() {
HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
using RtlGetVersionPtr = NTSTATUS(WINAPI *)(PRTL_OSVERSIONINFOW);
auto pRtlGetVersion = reinterpret_cast<RtlGetVersionPtr>(::GetProcAddress(hMod, "RtlGetVersion"));
RTL_OSVERSIONINFOW rovi{};
rovi.dwOSVersionInfoSize = sizeof(rovi);
pRtlGetVersion(&rovi);
return rovi;
}
RTL_OSVERSIONINFOW GetRealOSVersion() {
static const auto result = GetRealOSVersionImpl();
return result;
}
static inline bool isWin7Only() {
RTL_OSVERSIONINFOW rovi = GetRealOSVersion();
return rovi.dwMajorVersion = 7;
}
static inline bool isWin11OrGreater() {
RTL_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 10) ||
(rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22000);
}
static bool containsCursorToItem(QQuickItem *item) { static bool containsCursorToItem(QQuickItem *item) {
auto window = item->window(); auto window = item->window();
if ((window == nullptr) || !item || !item->isVisible()) { if ((window == nullptr) || !item || !item->isVisible()) {
@ -32,8 +148,189 @@ static bool containsCursorToItem(QQuickItem *item) {
return false; return false;
} }
static inline bool initializeFunctionPointers() {
HMODULE module = LoadLibraryW(L"dwmapi.dll");
if (module) {
if (!pDwmSetWindowAttribute) {
pDwmSetWindowAttribute =
reinterpret_cast<DwmSetWindowAttributeFunc>(GetProcAddress(module, "DwmSetWindowAttribute"));
if (!pDwmSetWindowAttribute) {
return false;
}
}
if (!pDwmExtendFrameIntoClientArea) {
pDwmExtendFrameIntoClientArea = reinterpret_cast<DwmExtendFrameIntoClientAreaFunc>(
GetProcAddress(module, "DwmExtendFrameIntoClientArea"));
if (!pDwmExtendFrameIntoClientArea) {
return false;
}
}
if (!pDwmIsCompositionEnabled) {
pDwmIsCompositionEnabled =
reinterpret_cast<DwmIsCompositionEnabledFunc>(::GetProcAddress(module, "DwmIsCompositionEnabled"));
if (!pDwmIsCompositionEnabled) {
return false;
}
}
if (!pDwmEnableBlurBehindWindow) {
pDwmEnableBlurBehindWindow =
reinterpret_cast<DwmEnableBlurBehindWindowFunc>(GetProcAddress(module, "DwmEnableBlurBehindWindow"));
if (!pDwmEnableBlurBehindWindow) {
return false;
}
}
if (!pSetWindowCompositionAttribute) {
HMODULE user32 = LoadLibraryW(L"user32.dll");
if (!user32) {
return false;
}
pSetWindowCompositionAttribute = reinterpret_cast<SetWindowCompositionAttributeFunc>(
GetProcAddress(user32, "SetWindowCompositionAttribute"));
if (!pSetWindowCompositionAttribute) {
return false;
}
}
}
return true;
}
static inline bool isWin1122H2OrGreater() {
RTL_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 10) ||
(rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22621);
}
static inline bool setWindowDarkMode(HWND hwnd, const BOOL enable) {
if (!initializeFunctionPointers()) {
return false;
}
return bool(pDwmSetWindowAttribute(hwnd, 20, &enable, sizeof(BOOL)));
}
static inline bool isCompositionEnabled() {
if (initializeFunctionPointers()) {
BOOL composition_enabled = false;
pDwmIsCompositionEnabled(&composition_enabled);
return composition_enabled;
}
return false;
}
static inline bool isWin8OrGreater() {
RTL_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 6) || (rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 2);
}
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() || !initializeFunctionPointers()) {
return false;
}
if (enable) {
pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins);
if (isWin1122H2OrGreater()) {
const DWORD backdropType = _DWMSBT_MAINWINDOW;
pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType));
} else {
const BOOL enable = TRUE;
pDwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable));
}
} else {
if (isWin1122H2OrGreater()) {
const DWORD backdropType = _DWMSBT_AUTO;
pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType));
} else {
const BOOL enable = FALSE;
pDwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable));
}
}
BOOL isDark = Theme::instance()->dark();
setWindowDarkMode(hwnd, isDark);
return true;
}
if (key == QStringLiteral("mica-alt")) {
if (!isWin1122H2OrGreater() || !initializeFunctionPointers()) {
return false;
}
if (enable) {
pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins);
const DWORD backdropType = _DWMSBT_TABBEDWINDOW;
pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType));
} else {
const DWORD backdropType = _DWMSBT_AUTO;
pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType));
}
BOOL isDark = Theme::instance()->dark();
setWindowDarkMode(hwnd, isDark);
return true;
}
if (key == QStringLiteral("acrylic")) {
if (!isWin11OrGreater() || !initializeFunctionPointers()) {
return false;
}
if (enable) {
pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins);
DWORD system_backdrop_type = _DWMSBT_TRANSIENTWINDOW;
pDwmSetWindowAttribute(hwnd, 38, &system_backdrop_type, sizeof(DWORD));
} else {
const DWORD backdropType = _DWMSBT_AUTO;
pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType));
}
BOOL isDark = Theme::instance()->dark();
setWindowDarkMode(hwnd, isDark);
return true;
}
if (key == QStringLiteral("dwm-blur")) {
if ((isWin7Only() && !isCompositionEnabled()) || !initializeFunctionPointers()) {
return false;
}
BOOL isDark = Theme::instance()->dark();
setWindowDarkMode(hwnd, isDark && enable);
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);
pSetWindowCompositionAttribute(hwnd, &wcad);
} else {
DWM_BLURBEHIND bb{};
bb.fEnable = TRUE;
bb.dwFlags = DWM_BB_ENABLE;
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);
pSetWindowCompositionAttribute(hwnd, &wcad);
} else {
DWM_BLURBEHIND bb{};
bb.fEnable = FALSE;
bb.dwFlags = DWM_BB_ENABLE;
pDwmEnableBlurBehindWindow(hwnd, &bb);
}
}
return true;
}
return false;
}
Frameless::Frameless(QQuickItem *parent) : QQuickItem{parent} { Frameless::Frameless(QQuickItem *parent) : QQuickItem{parent} {
m_isWindows11OrGreater = Utilities::instance()->isWindows11OrGreater(); m_isWindows11OrGreater = Utilities::instance()->isWindows11OrGreater();
m_effect = "normal";
} }
QQuickItem *Frameless::appBar() const { QQuickItem *Frameless::appBar() const {
@ -119,12 +416,36 @@ void Frameless::setHitTestVisible(QQuickItem *item) {
} }
} }
void Frameless::showMaximized() {
#ifdef Q_OS_WIN
HWND hwnd = reinterpret_cast<HWND>(window()->winId());
::ShowWindow(hwnd, 3);
#else
window()->setVisibility(QQuickWindow::Maximized);
#endif
}
void Frameless::showMinimized() {
#ifdef Q_OS_WIN
HWND hwnd = reinterpret_cast<HWND>(window()->winId());
::ShowWindow(hwnd, 2);
#else
window()->setVisibility(QQuickWindow::Minimized);
#endif
}
void Frameless::showNormal() {
window()->setVisibility(QQuickWindow::Windowed);
}
void Frameless::onDestruction() { void Frameless::onDestruction() {
QGuiApplication::instance()->removeNativeEventFilter(this); QGuiApplication::instance()->removeNativeEventFilter(this);
} }
void Frameless::componentComplete() { void Frameless::componentComplete() {
if (m_disabled) return; if (m_disabled) {
return;
}
int w = window()->width(); int w = window()->width();
int h = window()->height(); int h = window()->height();
m_current = window()->winId(); m_current = window()->winId();
@ -150,8 +471,12 @@ void Frameless::componentComplete() {
#endif #endif
HWND hwnd = reinterpret_cast<HWND>(window()->winId()); HWND hwnd = reinterpret_cast<HWND>(window()->winId());
DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE); DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE);
#if (QT_VERSION == QT_VERSION_CHECK(6, 7, 2))
style &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
#endif
if (m_fixSize) { if (m_fixSize) {
::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION); ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION);
;
for (int i = 0; i <= QGuiApplication::screens().count() - 1; ++i) { for (int i = 0; i <= QGuiApplication::screens().count() - 1; ++i) {
connect(QGuiApplication::screens().at(i), &QScreen::logicalDotsPerInchChanged, this, [=] { connect(QGuiApplication::screens().at(i), &QScreen::logicalDotsPerInchChanged, this, [=] {
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
@ -171,6 +496,42 @@ void Frameless::componentComplete() {
if (!window()->property("_hideShadow").toBool()) { if (!window()->property("_hideShadow").toBool()) {
setShadow(hwnd); setShadow(hwnd);
} }
if (isWin11OrGreater()) {
availableEffects({"mica", "mica-alt", "acrylic", "dwm-blur", "normal"});
} else if (isWin7Only()) {
availableEffects({"dwm-blur", "normal"});
}
if (!m_effect.isEmpty()) {
effective(setWindowEffect(hwnd, m_effect, true));
if (effective()) {
m_currentEffect = effect();
}
}
connect(this, &Frameless::effectChanged, this, [hwnd, this] {
if (effect() == m_currentEffect) {
return;
}
if (effective()) {
setWindowEffect(hwnd, m_currentEffect, false);
}
effective(setWindowEffect(hwnd, effect(), true));
if (effective()) {
m_currentEffect = effect();
} else {
m_effect = "normal";
m_currentEffect = "normal";
}
});
connect(Theme::instance(), &Theme::blurBehindWindowEnabledChanged, this, [this] {
if (Theme::instance()->blurBehindWindowEnabled()) {
effect("normal");
}
});
connect(Theme::instance(), &Theme::darkChanged, this, [hwnd, this] {
if (effective() && !m_currentEffect.isEmpty() && m_currentEffect != "normal") {
setWindowDarkMode(hwnd, Theme::instance()->dark());
}
});
#endif #endif
auto appBarHeight = m_appBar->height(); auto appBarHeight = m_appBar->height();
h = qRound(h + appBarHeight); h = qRound(h + appBarHeight);

View File

@ -1,12 +1,16 @@
#ifndef FRAMELESS_H #ifndef FRAMELESS_H
#define FRAMELESS_H #define FRAMELESS_H
#include "Utilities.h"
#include <QAbstractNativeEventFilter> #include <QAbstractNativeEventFilter>
#include <QQuickItem> #include <QQuickItem>
class Frameless : public QQuickItem, QAbstractNativeEventFilter { class Frameless : public QQuickItem, QAbstractNativeEventFilter {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY_AUTO(QString, effect)
Q_PROPERTY_READONLY_AUTO(bool, effective)
Q_PROPERTY_READONLY_AUTO(QStringList, availableEffects)
Q_PROPERTY(QQuickItem *appBar READ appBar WRITE setAppBar NOTIFY appBarChanged) Q_PROPERTY(QQuickItem *appBar READ appBar WRITE setAppBar NOTIFY appBarChanged)
Q_PROPERTY(QQuickItem *maximizeButton READ maximizeButton WRITE setMaximizeButton NOTIFY maximizeButtonChanged) Q_PROPERTY(QQuickItem *maximizeButton READ maximizeButton WRITE setMaximizeButton NOTIFY maximizeButtonChanged)
Q_PROPERTY(QQuickItem *minimizedButton READ minimizedButton WRITE setMinimizedButton NOTIFY minimizedButtonChanged) Q_PROPERTY(QQuickItem *minimizedButton READ minimizedButton WRITE setMinimizedButton NOTIFY minimizedButtonChanged)
@ -39,6 +43,10 @@ public:
bool disabled() const; bool disabled() const;
void setDisabled(bool disabled); void setDisabled(bool disabled);
Q_INVOKABLE void showMaximized();
Q_INVOKABLE void showMinimized();
Q_INVOKABLE void showNormal();
Q_INVOKABLE void setHitTestVisible(QQuickItem *item); Q_INVOKABLE void setHitTestVisible(QQuickItem *item);
Q_INVOKABLE void onDestruction(); Q_INVOKABLE void onDestruction();
void componentComplete() final; void componentComplete() final;
@ -77,6 +85,7 @@ private:
int m_margins = 8; int m_margins = 8;
QList<QPointer<QQuickItem>> m_hitTestList; QList<QPointer<QQuickItem>> m_hitTestList;
bool m_isWindows11OrGreater = false; bool m_isWindows11OrGreater = false;
QString m_currentEffect;
}; };
#endif // FRAMELESS_H #endif // FRAMELESS_H

48
Fluent/TextStyle.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "TextStyle.h"
TextStyle::TextStyle(QObject *parent) : QObject{parent} {
m_family = QFont().defaultFamily();
#ifdef Q_OS_WIN
m_family = "微软雅黑";
#endif
QFont caption;
caption.setFamily(m_family);
caption.setPixelSize(12);
Caption(caption);
QFont body;
body.setFamily(m_family);
body.setPixelSize(13);
Body(body);
QFont bodyStrong;
bodyStrong.setFamily(m_family);
bodyStrong.setPixelSize(13);
bodyStrong.setWeight(QFont::DemiBold);
BodyStrong(bodyStrong);
QFont subtitle;
subtitle.setFamily(m_family);
subtitle.setPixelSize(20);
subtitle.setWeight(QFont::DemiBold);
Subtitle(subtitle);
QFont title;
title.setFamily(m_family);
title.setPixelSize(28);
title.setWeight(QFont::DemiBold);
Title(title);
QFont titleLarge;
titleLarge.setFamily(m_family);
titleLarge.setPixelSize(40);
titleLarge.setWeight(QFont::DemiBold);
TitleLarge(titleLarge);
QFont display;
display.setFamily(m_family);
display.setPixelSize(68);
display.setWeight(QFont::DemiBold);
Display(display);
}

25
Fluent/TextStyle.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef __TEXTSTYLE_H__
#define __TEXTSTYLE_H__
#include "Utilities.h"
#include <QFont>
#include <QObject>
class TextStyle : public QObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY_AUTO(QString, family)
Q_PROPERTY_AUTO(QFont, Caption)
Q_PROPERTY_AUTO(QFont, Body)
Q_PROPERTY_AUTO(QFont, BodyStrong)
Q_PROPERTY_AUTO(QFont, Subtitle)
Q_PROPERTY_AUTO(QFont, Title)
Q_PROPERTY_AUTO(QFont, TitleLarge)
Q_PROPERTY_AUTO(QFont, Display)
public:
TextStyle(QObject *parent = nullptr);
};
#endif // __TEXTSTYLE_H__

View File

@ -1,6 +1,56 @@
#include "Theme.h" #include "Theme.h"
#include "Colors.h"
#include <QGuiApplication>
#include <QPalette>
#include <QThreadPool>
static bool systemDark() {
QPalette palette = QGuiApplication::palette();
QColor color = palette.color(QPalette::Window).rgb();
return color.red() * 0.2126 + color.green() * 0.7152 + color.blue() * 0.0722 <= 255.0f / 2;
}
Theme *Theme::instance() {
static Theme *self = nullptr;
if (self == nullptr) {
self = new Theme();
}
return self;
}
Theme *Theme::create(QQmlEngine *, QJSEngine *) {
auto ret = instance();
QJSEngine::setObjectOwnership(ret, QJSEngine::CppOwnership);
return ret;
}
Theme::Theme(QObject *parent) : QObject{parent} { Theme::Theme(QObject *parent) : QObject{parent} {
m_accentColor = Colors::instance()->Blue();
m_darkMode = ThemeType::DarkMode::Light;
m_nativeText = false;
m_animationEnabled = true;
m_systemDark = systemDark();
m_desktopImagePath = "";
m_blurBehindWindowEnabled = false;
QGuiApplication::instance()->installEventFilter(this);
refreshColors();
connect(this, &Theme::darkModeChanged, this, [=] { Q_EMIT darkChanged(); });
connect(this, &Theme::darkChanged, this, [=] { refreshColors(); });
connect(this, &Theme::accentColorChanged, this, [=] { refreshColors(); });
connect(&m_watcher, &QFileSystemWatcher::fileChanged, this,
[=](const QString &path) { Q_EMIT desktopImagePathChanged(); });
connect(this, &Theme::blurBehindWindowEnabledChanged, this, [=] { checkUpdateDesktopImage(); });
startTimer(1000);
}
bool Theme::dark() const {
if (m_darkMode == ThemeType::DarkMode::Dark) {
return true;
} else if (m_darkMode == ThemeType::DarkMode::System) {
return m_systemDark;
} else {
return false;
}
} }
QColor Theme::fontPrimaryColor() const { QColor Theme::fontPrimaryColor() const {
@ -79,3 +129,53 @@ void Theme::setBlurBehindWindowEnabled(bool enabled) {
emit blurBehindWindowEnabledChanged(); emit blurBehindWindowEnabledChanged();
} }
} }
void Theme::refreshColors() {
auto isDark = dark();
primaryColor(isDark ? m_accentColor->lighter() : m_accentColor->dark());
backgroundColor(isDark ? QColor(0, 0, 0, 255) : QColor(255, 255, 255, 255));
dividerColor(isDark ? QColor(80, 80, 80, 255) : QColor(210, 210, 210, 255));
setWindowBackgroundColor(isDark ? QColor(32, 32, 32, 255) : QColor(237, 237, 237, 255));
setWindowActiveBackgroundColor(isDark ? QColor(26, 26, 26, 255) : QColor(243, 243, 243, 255));
setFontPrimaryColor(isDark ? QColor(248, 248, 248, 255) : QColor(7, 7, 7, 255));
fontSecondaryColor(isDark ? QColor(222, 222, 222, 255) : QColor(102, 102, 102, 255));
fontTertiaryColor(isDark ? QColor(200, 200, 200, 255) : QColor(153, 153, 153, 255));
setItemNormalColor(isDark ? QColor(255, 255, 255, 0) : QColor(0, 0, 0, 0));
frameColor(isDark ? QColor(56, 56, 56, qRound(255 * 0.8)) : QColor(243, 243, 243, qRound(255 * 0.8)));
frameActiveColor(isDark ? QColor(48, 48, 48, qRound(255 * 0.8)) : QColor(255, 255, 255, qRound(255 * 0.8)));
setItemHoverColor(isDark ? QColor(255, 255, 255, qRound(255 * 0.06)) : QColor(0, 0, 0, qRound(255 * 0.03)));
itemPressColor(isDark ? QColor(255, 255, 255, qRound(255 * 0.09)) : QColor(0, 0, 0, qRound(255 * 0.06)));
itemCheckColor(isDark ? QColor(255, 255, 255, qRound(255 * 0.12)) : QColor(0, 0, 0, qRound(255 * 0.09)));
}
void Theme::checkUpdateDesktopImage() {
if (!m_blurBehindWindowEnabled) {
return;
}
QThreadPool::globalInstance()->start([=]() {
m_mutex.lock();
auto path = Utilities::instance()->getWallpaperFilePath();
if (m_desktopImagePath != path) {
if (!m_desktopImagePath.isEmpty()) {
m_watcher.removePath(m_desktopImagePath);
}
setDesktopImagePath(path);
m_watcher.addPath(path);
}
m_mutex.unlock();
});
}
bool Theme::eventFilter(QObject *, QEvent *event) {
if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::ThemeChange) {
m_systemDark = systemDark();
Q_EMIT darkChanged();
event->accept();
return true;
}
return false;
}
void Theme::timerEvent(QTimerEvent *event) {
checkUpdateDesktopImage();
}

View File

@ -1,7 +1,11 @@
#ifndef THEME_H #ifndef THEME_H
#define THEME_H #define THEME_H
#include "AccentColor.h"
#include "Utilities.h"
#include <QColor> #include <QColor>
#include <QFileSystemWatcher>
#include <QMutex>
#include <QObject> #include <QObject>
#include <QQmlEngine> #include <QQmlEngine>
@ -9,7 +13,21 @@ class Theme : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
Q_PROPERTY(bool dark READ dark NOTIFY darkChanged)
Q_PROPERTY_AUTO_P(AccentColor *, accentColor)
Q_PROPERTY_AUTO(int, darkMode)
Q_PROPERTY_AUTO(QColor, primaryColor)
Q_PROPERTY_AUTO(QColor, backgroundColor)
Q_PROPERTY_AUTO(QColor, dividerColor)
Q_PROPERTY_AUTO(QColor, itemPressColor)
Q_PROPERTY_AUTO(bool, nativeText)
Q_PROPERTY_AUTO(bool, animationEnabled)
Q_PROPERTY(QColor fontPrimaryColor READ fontPrimaryColor WRITE setFontPrimaryColor NOTIFY fontPrimaryColorChanged) Q_PROPERTY(QColor fontPrimaryColor READ fontPrimaryColor WRITE setFontPrimaryColor NOTIFY fontPrimaryColorChanged)
Q_PROPERTY_AUTO(QColor, fontSecondaryColor)
Q_PROPERTY_AUTO(QColor, fontTertiaryColor)
Q_PROPERTY_AUTO(QColor, frameColor)
Q_PROPERTY_AUTO(QColor, frameActiveColor)
Q_PROPERTY_AUTO(QColor, itemCheckColor)
Q_PROPERTY(QColor itemNormalColor READ itemNormalColor WRITE setItemNormalColor NOTIFY itemNormalColorChanged) Q_PROPERTY(QColor itemNormalColor READ itemNormalColor WRITE setItemNormalColor NOTIFY itemNormalColorChanged)
Q_PROPERTY(QColor itemHoverColor READ itemHoverColor WRITE setItemHoverColor NOTIFY itemHoverColorChanged) Q_PROPERTY(QColor itemHoverColor READ itemHoverColor WRITE setItemHoverColor NOTIFY itemHoverColorChanged)
@ -23,7 +41,9 @@ class Theme : public QObject {
blurBehindWindowEnabledChanged) blurBehindWindowEnabledChanged)
public: public:
Theme(QObject *parent = nullptr); static Theme *instance();
static Theme *create(QQmlEngine *, QJSEngine *);
bool dark() const;
QColor fontPrimaryColor() const; QColor fontPrimaryColor() const;
void setFontPrimaryColor(const QColor &color); void setFontPrimaryColor(const QColor &color);
@ -47,6 +67,7 @@ public:
void setBlurBehindWindowEnabled(bool enabled); void setBlurBehindWindowEnabled(bool enabled);
signals: signals:
void darkChanged();
void fontPrimaryColorChanged(); void fontPrimaryColorChanged();
void itemNormalColorChanged(); void itemNormalColorChanged();
void windowBackgroundColorChanged(); void windowBackgroundColorChanged();
@ -55,7 +76,17 @@ signals:
void blurBehindWindowEnabledChanged(); void blurBehindWindowEnabledChanged();
void itemHoverColorChanged(); void itemHoverColorChanged();
protected:
Theme(QObject *parent = nullptr);
void refreshColors();
void checkUpdateDesktopImage();
void timerEvent(QTimerEvent *event) final;
bool eventFilter(QObject *obj, QEvent *event) final;
private: private:
bool m_systemDark = false;
QMutex m_mutex;
QFileSystemWatcher m_watcher;
QColor m_fontPrimaryColor; QColor m_fontPrimaryColor;
QColor m_itemNormalColor; QColor m_itemNormalColor;
QColor m_itemHoverColor; QColor m_itemHoverColor;

View File

@ -1,5 +1,6 @@
#include "Utilities.h" #include "Utilities.h"
#include <QSettings> #include <QSettings>
#include <qt_windows.h>
Utilities *Utilities::instance() { Utilities *Utilities::instance() {
static Utilities *self = nullptr; static Utilities *self = nullptr;
@ -18,10 +19,70 @@ Utilities *Utilities::create(QQmlEngine *, QJSEngine *) {
Utilities::Utilities(QObject *parent) : QObject{parent} { Utilities::Utilities(QObject *parent) : QObject{parent} {
} }
bool Utilities::isSoftware() {
return QQuickWindow::sceneGraphBackend() == "software";
}
void Utilities::deleteLater(QObject *p) {
if (p) {
p->deleteLater();
}
}
QColor Utilities::withOpacity(const QColor &color, qreal opacity) {
int alpha = qRound(opacity * 255) & 0xff;
return QColor::fromRgba((alpha << 24) | (color.rgba() & 0xffffff));
}
QRect Utilities::desktopAvailableGeometry(QQuickWindow *window) { QRect Utilities::desktopAvailableGeometry(QQuickWindow *window) {
return window->screen()->availableGeometry(); return window->screen()->availableGeometry();
} }
QString Utilities::getWallpaperFilePath() {
#if defined(Q_OS_WIN)
wchar_t path[MAX_PATH] = {};
if (::SystemParametersInfoW(SPI_GETDESKWALLPAPER, MAX_PATH, path, FALSE) == FALSE) {
return {};
}
return QString::fromWCharArray(path);
#elif defined(Q_OS_LINUX)
auto type = QSysInfo::productType();
if (type == "uos") {
QProcess process;
QStringList args;
args << "--session";
args << "--type=method_call";
args << "--print-reply";
args << "--dest=com.deepin.wm";
args << "/com/deepin/wm";
args << "com.deepin.wm.GetCurrentWorkspaceBackgroundForMonitor";
args << QString("string:'%1'").arg(currentTimestamp());
process.start("dbus-send", args);
process.waitForFinished();
QByteArray result = process.readAllStandardOutput().trimmed();
int startIndex = result.indexOf("file:///");
if (startIndex != -1) {
auto path = result.mid(startIndex + 7, result.length() - startIndex - 8);
return path;
}
}
#elif defined(Q_OS_MACOS)
QProcess process;
QStringList args;
args << "-e";
args << R"(tell application "Finder" to get POSIX path of (desktop picture as alias))";
process.start("osascript", args);
process.waitForFinished();
QByteArray result = process.readAllStandardOutput().trimmed();
if (result.isEmpty()) {
return "/System/Library/CoreServices/DefaultDesktop.heic";
}
return result;
#else
return {};
#endif
}
QUrl Utilities::getUrlByFilePath(const QString &path) { QUrl Utilities::getUrlByFilePath(const QString &path) {
return QUrl::fromLocalFile(path); return QUrl::fromLocalFile(path);
} }

View File

@ -5,6 +5,84 @@
#include <QQmlEngine> #include <QQmlEngine>
#include <QQuickWindow> #include <QQuickWindow>
#define Q_PROPERTY_AUTO(TYPE, M) \
Q_PROPERTY(TYPE M MEMBER m_##M NOTIFY M##Changed) \
public: \
Q_SIGNAL void M##Changed(); \
void M(const TYPE &in_##M) { \
m_##M = in_##M; \
Q_EMIT M##Changed(); \
} \
TYPE M() { \
return m_##M; \
} \
\
private: \
TYPE m_##M;
#define Q_PROPERTY_READONLY_AUTO(TYPE, M) \
Q_PROPERTY(TYPE M READ M NOTIFY M##Changed FINAL) \
public: \
Q_SIGNAL void M##Changed(); \
void M(const TYPE &in_##M) { \
m_##M = in_##M; \
Q_EMIT M##Changed(); \
} \
TYPE M() { \
return m_##M; \
} \
\
private: \
TYPE m_##M;
#define Q_PROPERTY_AUTO_P(TYPE, M) \
Q_PROPERTY(TYPE M MEMBER m_##M NOTIFY M##Changed) \
public: \
Q_SIGNAL void M##Changed(); \
void M(TYPE in_##M) { \
m_##M = in_##M; \
Q_EMIT M##Changed(); \
} \
TYPE M() { \
return m_##M; \
} \
\
private: \
TYPE m_##M;
namespace WindowType {
Q_NAMESPACE
enum LaunchMode {
Standard = 0x0000,
SingleTask = 0x0001,
SingleInstance = 0x0002,
};
Q_ENUM_NS(LaunchMode)
QML_ELEMENT
} // namespace WindowType
namespace ThemeType {
Q_NAMESPACE
enum DarkMode {
System = 0x0000,
Light = 0x0001,
Dark = 0x0002,
};
Q_ENUM_NS(DarkMode)
QML_ELEMENT
} // namespace ThemeType
namespace ContentDialogType {
Q_NAMESPACE
enum ButtonFlag {
NeutralButton = 0x0001,
NegativeButton = 0x0002,
PositiveButton = 0x0004,
};
Q_ENUM_NS(ButtonFlag)
QML_ELEMENT
} // namespace ContentDialogType
class Utilities : public QObject { class Utilities : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
@ -18,6 +96,10 @@ public:
Q_INVOKABLE bool isMacos(); Q_INVOKABLE bool isMacos();
Q_INVOKABLE QRect desktopAvailableGeometry(QQuickWindow *window); Q_INVOKABLE QRect desktopAvailableGeometry(QQuickWindow *window);
Q_INVOKABLE QUrl getUrlByFilePath(const QString &path); Q_INVOKABLE QUrl getUrlByFilePath(const QString &path);
Q_INVOKABLE bool isSoftware();
Q_INVOKABLE void deleteLater(QObject *p);
Q_INVOKABLE QColor withOpacity(const QColor &, qreal alpha);
Q_INVOKABLE QString getWallpaperFilePath();
protected: protected:
Utilities(QObject *parent = nullptr); Utilities(QObject *parent = nullptr);

View File

@ -1,33 +1,33 @@
import QtQuick import QtQuick
Item { Item {
id: control id: control
property color tintColor: Qt.rgba(1, 1, 1, 1) property color tintColor: Qt.rgba(1, 1, 1, 1)
property real tintOpacity: 0.65 property real tintOpacity: 0.65
property real luminosity: 0.01 property real luminosity: 0.01
property real noiseOpacity: 0.02 property real noiseOpacity: 0.02
property var target property var target
property int blurRadius: 32 property int blurRadius: 32
property rect targetRect: Qt.rect(control.x, control.y, control.width,control.height) property rect targetRect: Qt.rect(control.x, control.y, control.width,control.height)
ShaderEffectSource { ShaderEffectSource {
id: effect_source id: effect_source
anchors.fill: parent anchors.fill: parent
visible: false visible: false
sourceRect: control.targetRect sourceRect: control.targetRect
sourceItem: control.target sourceItem: control.target
} }
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(1, 1, 1, luminosity) color: Qt.rgba(1, 1, 1, luminosity)
} }
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(tintColor.r, tintColor.g, tintColor.b, tintOpacity) color: Qt.rgba(tintColor.r, tintColor.g, tintColor.b, tintOpacity)
} }
Image { Image {
anchors.fill: parent anchors.fill: parent
source: "qrc:/qt/qml/Fluent/resources/noise.png" source: "qrc:/qt/qml/Fluent/resources/noise.png"
fillMode: Image.Tile fillMode: Image.Tile
opacity: control.noiseOpacity opacity: control.noiseOpacity
} }
} }

View File

@ -1,87 +1,281 @@
import QtQuick as Quick import QtQuick as Quick
import QtQuick.Layouts import QtQuick.Controls
import Fluent import QtQuick.Window
import QtQuick.Layouts
Quick.Rectangle { import Fluent
id: root
property bool showMinimize: true Quick.Rectangle{
property bool showMaximize: true property string title: ""
property bool showClose: true property string darkText : qsTr("Dark")
property bool showStayTop: true property string lightText : qsTr("Light")
property bool showDark: false property string minimizeText : qsTr("Minimize")
property string title: "" property string restoreText : qsTr("Restore")
property url icon property string maximizeText : qsTr("Maximize")
property string maximizeText : qsTr("Maximize") property string closeText : qsTr("Close")
property Quick.color textColor: Theme.fontPrimaryColor property string stayTopText : qsTr("Sticky on Top")
property Quick.color maximizeNormalColor: Theme.itemNormalColor property string stayTopCancelText : qsTr("Sticky on Top cancelled")
property Quick.color maximizeHoverColor: Theme.itemHoverColor property color textColor: Theme.fontPrimaryColor
property bool isMac: Utilities.isMacos() property color minimizeNormalColor: Theme.itemNormalColor
property alias buttonMaximize: btn_maximize property color minimizeHoverColor: Theme.itemHoverColor
property alias layoutMacosButtons: layout_macos_buttons property color minimizePressColor: Theme.itemPressColor
property alias layoutStandardbuttons: layout_standard_buttons property color maximizeNormalColor: Theme.itemNormalColor
property color maximizeHoverColor: Theme.itemHoverColor
Quick.Item{ property color maximizePressColor: Theme.itemPressColor
id:d property color closeNormalColor: Qt.rgba(0,0,0,0)
property var hitTestList: [] property color closeHoverColor: Qt.rgba(251/255,115/255,115/255,1)
property bool hoverMaxBtn: false property color closePressColor: Qt.rgba(251/255,115/255,115/255,0.8)
property var win: Window.window property bool showDark: false
property bool stayTop: { property bool showClose: true
if(d.win instanceof Window){ property bool showMinimize: true
return d.win.stayTop property bool showMaximize: true
} property bool showStayTop: true
return false property bool titleVisible: true
} property url icon
property bool isRestore: win && (Window.Maximized === win.visibility || Window.FullScreen === win.visibility) property int iconSize: 20
property bool resizable: win && !(win.height === win.maximumHeight && win.height === win.minimumHeight && win.width === win.maximumWidth && win.width === win.minimumWidth) property bool isMac: Utilities.isMacos()
function containsPointToItem(point,item){ property color borerlessColor : Theme.primaryColor
var pos = item.mapToGlobal(0,0) property alias buttonStayTop: btn_stay_top
var rect = Qt.rect(pos.x,pos.y,item.width,item.height) property alias buttonMinimize: btn_minimize
if(point.x>rect.x && point.x<(rect.x+rect.width) && point.y>rect.y && point.y<(rect.y+rect.height)){ property alias buttonMaximize: btn_maximize
return true property alias buttonClose: btn_close
} property alias buttonDark: btn_dark
return false property alias layoutMacosButtons: layout_macos_buttons
} property alias layoutStandardbuttons: layout_standard_buttons
} property var maxClickListener : function(){
if(Utilities.isMacos()){
RowLayout { if (d.win.visibility === Window.FullScreen || d.win.visibility === Window.Maximized)
id:layout_standard_buttons d.win.showNormal()
height: parent.height else
anchors.right: parent.right d.win.showFullScreen()
spacing: 0 }else{
if (d.win.visibility === Window.Maximized || d.win.visibility === Window.FullScreen)
IconButton{ d.win.showNormal()
id:btn_maximize else
property bool hover: btn_maximize.hovered d.win.showMaximized()
Layout.preferredWidth: 40 d.hoverMaxBtn = false
Layout.preferredHeight: 30 }
padding: 0 }
verticalPadding: 0 property var minClickListener: function(){
horizontalPadding: 0 if(d.win.transientParent != null){
iconSource : d.isRestore ? Icons.ChromeRestore : Icons.ChromeMaximize d.win.transientParent.showMinimized()
color: { }else{
if(down){ d.win.showMinimized()
return maximizePressColor }
} }
return btn_maximize.hover ? maximizeHoverColor : maximizeNormalColor property var closeClickListener : function(){
} d.win.close()
Layout.alignment: Qt.AlignVCenter }
visible: d.resizable && !isMac && showMaximize property var stayTopClickListener: function(){
radius: 0 if(d.win instanceof Window){
iconColor: root.textColor d.win.stayTop = !d.win.stayTop
text:d.isRestore?restoreText:maximizeText }
iconSize: 11 }
onClicked: maxClickListener() property var darkClickListener: function(){
} if(Theme.dark){
} Theme.darkMode = ThemeType.Light
}else{
Quick.Loader{ Theme.darkMode = ThemeType.Dark
id:layout_macos_buttons }
anchors{ }
verticalCenter: parent.verticalCenter id:control
left: parent.left color: Qt.rgba(0,0,0,0)
leftMargin: 10 height: visible ? 30 : 0
} opacity: visible
sourceComponent: isMac ? com_macos_buttons : undefined z: 65535
Quick.Component.onDestruction: sourceComponent = undefined Item{
} id:d
} property var hitTestList: []
property bool hoverMaxBtn: false
property var win: Window.window
property bool stayTop: {
if(d.win instanceof Window){
return d.win.stayTop
}
return false
}
property bool isRestore: win && (Window.Maximized === win.visibility || Window.FullScreen === win.visibility)
property bool resizable: win && !(win.height === win.maximumHeight && win.height === win.minimumHeight && win.width === win.maximumWidth && win.width === win.minimumWidth)
function containsPointToItem(point,item){
var pos = item.mapToGlobal(0,0)
var rect = Qt.rect(pos.x,pos.y,item.width,item.height)
if(point.x>rect.x && point.x<(rect.x+rect.width) && point.y>rect.y && point.y<(rect.y+rect.height)){
return true
}
return false
}
}
Row{
anchors{
verticalCenter: parent.verticalCenter
left: isMac ? undefined : parent.left
leftMargin: isMac ? undefined : 10
horizontalCenter: isMac ? parent.horizontalCenter : undefined
}
spacing: 10
Image{
width: control.iconSize
height: control.iconSize
visible: status === Image.Ready ? true : false
source: control.icon
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: title
visible: control.titleVisible
color:control.textColor
anchors.verticalCenter: parent.verticalCenter
}
}
Component{
id:com_macos_buttons
RowLayout{
ImageButton{
Layout.preferredHeight: 12
Layout.preferredWidth: 12
normalImage: "../Image/btn_close_normal.png"
hoveredImage: "../Image/btn_close_hovered.png"
pushedImage: "../Image/btn_close_pushed.png"
visible: showClose
onClicked: closeClickListener()
}
ImageButton{
Layout.preferredHeight: 12
Layout.preferredWidth: 12
normalImage: "../Image/btn_min_normal.png"
hoveredImage: "../Image/btn_min_hovered.png"
pushedImage: "../Image/btn_min_pushed.png"
onClicked: minClickListener()
visible: showMinimize
}
ImageButton{
Layout.preferredHeight: 12
Layout.preferredWidth: 12
normalImage: "../Image/btn_max_normal.png"
hoveredImage: "../Image/btn_max_hovered.png"
pushedImage: "../Image/btn_max_pushed.png"
onClicked: maxClickListener()
visible: d.resizable && showMaximize
}
}
}
RowLayout{
id:layout_standard_buttons
height: parent.height
anchors.right: parent.right
spacing: 0
IconButton{
id:btn_dark
Layout.preferredWidth: 40
Layout.preferredHeight: 30
padding: 0
verticalPadding: 0
horizontalPadding: 0
rightPadding: 2
iconSource: Theme.dark ? Icons.Brightness : Icons.QuietHours
Layout.alignment: Qt.AlignVCenter
iconSize: 15
visible: showDark
text: Theme.dark ? control.lightText : control.darkText
radius: 0
iconColor:control.textColor
onClicked:()=> darkClickListener(btn_dark)
}
IconButton{
id:btn_stay_top
Layout.preferredWidth: 40
Layout.preferredHeight: 30
padding: 0
verticalPadding: 0
horizontalPadding: 0
iconSource : Icons.Pinned
Layout.alignment: Qt.AlignVCenter
iconSize: 14
visible: {
if(!(d.win instanceof Window)){
return false
}
return showStayTop
}
text:d.stayTop ? control.stayTopCancelText : control.stayTopText
radius: 0
iconColor: d.stayTop ? Theme.primaryColor : control.textColor
onClicked: stayTopClickListener()
}
IconButton{
id:btn_minimize
Layout.preferredWidth: 40
Layout.preferredHeight: 30
padding: 0
verticalPadding: 0
horizontalPadding: 0
iconSource : Icons.ChromeMinimize
Layout.alignment: Qt.AlignVCenter
iconSize: 11
text:minimizeText
radius: 0
visible: !isMac && showMinimize
iconColor: control.textColor
color: {
if(pressed){
return minimizePressColor
}
return hovered ? minimizeHoverColor : minimizeNormalColor
}
onClicked: minClickListener()
}
IconButton{
id:btn_maximize
property bool hover: btn_maximize.hovered
Layout.preferredWidth: 40
Layout.preferredHeight: 30
padding: 0
verticalPadding: 0
horizontalPadding: 0
iconSource : d.isRestore ? Icons.ChromeRestore : Icons.ChromeMaximize
color: {
if(down){
return maximizePressColor
}
return btn_maximize.hover ? maximizeHoverColor : maximizeNormalColor
}
Layout.alignment: Qt.AlignVCenter
visible: d.resizable && !isMac && showMaximize
radius: 0
iconColor: control.textColor
text:d.isRestore?restoreText:maximizeText
iconSize: 11
onClicked: maxClickListener()
}
IconButton{
id:btn_close
Layout.preferredWidth: 40
Layout.preferredHeight: 30
padding: 0
verticalPadding: 0
horizontalPadding: 0
iconSource : Icons.ChromeClose
Layout.alignment: Qt.AlignVCenter
text:closeText
visible: !isMac && showClose
radius: 0
iconSize: 10
iconColor: hovered ? Qt.rgba(1,1,1,1) : control.textColor
color:{
if(pressed){
return closePressColor
}
return hovered ? closeHoverColor : closeNormalColor
}
onClicked: closeClickListener()
}
}
Loader{
id:layout_macos_buttons
anchors{
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: 10
}
sourceComponent: isMac ? com_macos_buttons : undefined
}
}

64
Fluent/qml/Button.qml Normal file
View File

@ -0,0 +1,64 @@
import QtQuick as Quick
import QtQuick.Controls as Control
import Fluent
Control.Button {
property bool disabled: false
property string contentDescription: ""
property Quick.color normalColor: Theme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(254/255,254/255,254/255,1)
property Quick.color hoverColor: Theme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(246/255,246/255,246/255,1)
property Quick.color disableColor: Theme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(251/255,251/255,251/255,1)
property Quick.color dividerColor: Theme.dark ? Qt.rgba(80/255,80/255,80/255,1) : Qt.rgba(233/255,233/255,233/255,1)
property Quick.color textColor: {
if(Theme.dark){
if(!enabled){
return Qt.rgba(131/255,131/255,131/255,1)
}
if(pressed){
return Qt.rgba(162/255,162/255,162/255,1)
}
return Qt.rgba(1,1,1,1)
}else{
if(!enabled){
return Qt.rgba(160/255,160/255,160/255,1)
}
if(pressed){
return Qt.rgba(96/255,96/255,96/255,1)
}
return Qt.rgba(0,0,0,1)
}
}
Quick.Accessible.role: Quick.Accessible.Button
Quick.Accessible.name: control.text
Quick.Accessible.description: contentDescription
Quick.Accessible.onPressAction: control.clicked()
id: control
enabled: !disabled
verticalPadding: 0
horizontalPadding:12
font:TextStyle.Body
focusPolicy:Qt.TabFocus
background: ControlBackground{
implicitWidth: 30
implicitHeight: 30
radius: 4
color: {
if(!enabled){
return disableColor
}
return hovered ? hoverColor :normalColor
}
shadow: !pressed && enabled
FocusRectangle{
visible: control.activeFocus
radius:4
}
}
contentItem: Text {
text: control.text
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font: control.font
color: control.textColor
}
}

View File

@ -0,0 +1,164 @@
import QtQuick as Quick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import QtQuick.Window
import Fluent
Popup {
id: control
property string title: ""
property string message: ""
property string neutralText: qsTr("Close")
property string negativeText: qsTr("Cancel")
property string positiveText: qsTr("OK")
property int messageTextFormart: Text.AutoText
property int delayTime: 100
property int buttonFlags: FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton
property var contentDelegate: Component{
Item{
}
}
property var onNeutralClickListener
property var onNegativeClickListener
property var onPositiveClickListener
signal neutralClicked
signal negativeClicked
signal positiveClicked
implicitWidth: 400
implicitHeight: layout_content.height
focus: true
Component{
id:com_message
Flickable{
id:sroll_message
contentHeight: text_message.height
contentWidth: width
clip: true
boundsBehavior:Flickable.StopAtBounds
width: parent.width
height: message === "" ? 0 : Math.min(text_message.height,300)
Controls.ScrollBar.vertical: ScrollBar {}
Text{
id:text_message
font: TextStyle.Body
wrapMode: Text.WrapAnywhere
text:message
width: parent.width
topPadding: 4
leftPadding: 20
rightPadding: 20
bottomPadding: 4
}
}
}
Rectangle {
id:layout_content
width: parent.width
height: layout_column.childrenRect.height
color: 'transparent'
radius:5
ColumnLayout{
id:layout_column
width: parent.width
Text{
id:text_title
font: TextStyle.Title
text:title
topPadding: 20
leftPadding: 20
rightPadding: 20
wrapMode: Text.WrapAnywhere
}
Loader{
sourceComponent: com_message
Layout.fillWidth: true
Layout.preferredHeight: status===Loader.Ready ? item.height : 0
}
Loader{
sourceComponent:control.visible ? control.contentDelegate : undefined
Layout.fillWidth: true
onStatusChanged: {
if(status===Loader.Ready){
Layout.preferredHeight = item.implicitHeight
}else{
Layout.preferredHeight = 0
}
}
}
Rectangle{
id:layout_actions
Layout.fillWidth: true
Layout.preferredHeight: 60
radius: 5
color: Theme.dark ? Qt.rgba(32/255,32/255,32/255,1) : Qt.rgba(243/255,243/255,243/255,1)
RowLayout{
anchors
{
centerIn: parent
margins: spacing
fill: parent
}
spacing: 10
Item{
Layout.fillWidth: true
Layout.fillHeight: true
Button{
id:neutral_btn
visible: control.buttonFlags&ContentDialogType.NeutralButton
text: neutralText
width: parent.width
anchors.centerIn: parent
onClicked: {
if(control.onNeutralClickListener){
control.onNeutralClickListener()
}else{
neutralClicked()
control.close()
}
}
}
}
Item{
Layout.fillWidth: true
Layout.fillHeight: true
Button{
id:negative_btn
visible: control.buttonFlags&ContentDialogType.NegativeButton
width: parent.width
anchors.centerIn: parent
text: negativeText
onClicked: {
if(control.onNegativeClickListener){
control.onNegativeClickListener()
}else{
negativeClicked()
control.close()
}
}
}
}
Item{
Layout.fillWidth: true
Layout.fillHeight: true
FilledButton{
id:positive_btn
visible: control.buttonFlags&ContentDialogType.PositiveButton
text: positiveText
width: parent.width
anchors.centerIn: parent
onClicked: {
if(control.onPositiveClickListener){
control.onPositiveClickListener()
}else{
positiveClicked()
control.close()
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,52 @@
import QtQuick as Quick
import QtQuick.Controls
import Fluent
Quick.Item{
id:control
property int radius: 4
property bool shadow: true
property alias border: d.border
property var bottomMargin: undefined
property var topMargin: undefined
property var leftMargin: undefined
property var rightMargin: undefined
property Quick.color color: Theme.dark ? Qt.rgba(42/255,42/255,42/255,1) : Qt.rgba(254/255,254/255,254/255,1)
property alias gradient : rect_border.gradient
Quick.Rectangle{
id:d
property Quick.color startColor: Qt.lighter(d.border.color,1.25)
property Quick.color endColor: shadow ? control.border.color : startColor
visible: false
border.color: Theme.dark ? Qt.rgba(48/255,48/255,48/255,1) : Qt.rgba(188/255,188/255,188/255,1)
}
Quick.Rectangle{
id:rect_border
anchors.fill: parent
radius: control.radius
gradient: Quick.Gradient {
Quick.GradientStop { position: 0.0; color: d.startColor }
Quick.GradientStop { position: 1 - 3/control.height; color: d.startColor }
Quick.GradientStop { position: 1.0; color: d.endColor}
}
}
Quick.Rectangle{
id:rect_back
anchors{
fill: parent
margins: control.border.width
topMargin: control.topMargin
bottomMargin: control.bottomMargin
leftMargin: control.leftMargin
rightMargin: control.rightMargin
}
Quick.Behavior on anchors.bottomMargin {
Quick.NumberAnimation{
easing.type: Easing.OutCubic
duration: 167
}
}
radius: control.radius
color: control.color
}
}

View File

@ -0,0 +1,60 @@
import QtQuick as Quick
import QtQuick.Controls
import Fluent
Button {
property bool disabled: false
property string contentDescription: ""
property Quick.color normalColor: Theme.primaryColor
property Quick.color hoverColor: Theme.dark ? Qt.darker(normalColor,1.1) : Qt.lighter(normalColor,1.1)
property Quick.color disableColor: Theme.dark ? Qt.rgba(82/255,82/255,82/255,1) : Qt.rgba(199/255,199/255,199/255,1)
property Quick.color pressedColor: Theme.dark ? Qt.darker(normalColor,1.2) : Qt.lighter(normalColor,1.2)
property Quick.color textColor: {
if(Theme.dark){
if(!enabled){
return Qt.rgba(173/255,173/255,173/255,1)
}
return Qt.rgba(0,0,0,1)
}else{
return Qt.rgba(1,1,1,1)
}
}
Quick.Accessible.role: Quick.Accessible.Button
Quick.Accessible.name: control.text
Quick.Accessible.description: contentDescription
Quick.Accessible.onPressAction: control.clicked()
id: control
enabled: !disabled
focusPolicy:Qt.TabFocus
font:TextStyle.Body
verticalPadding: 0
horizontalPadding:12
background: ControlBackground{
implicitWidth: 30
implicitHeight: 30
radius: 4
bottomMargin: enabled ? 2 : 0
border.width: enabled ? 1 : 0
border.color: enabled ? Qt.darker(control.normalColor,1.2) : disableColor
color:{
if(!enabled){
return disableColor
}
if(pressed){
return pressedColor
}
return hovered ? hoverColor :normalColor
}
FocusRectangle{
visible: control.visualFocus
radius:4
}
}
contentItem: Text {
text: control.text
font: control.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: control.textColor
}
}

View File

@ -0,0 +1,20 @@
import QtQuick as Quick
import QtQuick.Controls
import Fluent
Quick.Item {
property int radius: 4
id:control
anchors.fill: parent
Quick.Rectangle{
width: control.width
height: control.height
anchors.centerIn: parent
color: "#00000000"
border.width: 2
radius: control.radius
border.color: Theme.dark ? Qt.rgba(1,1,1,1) : Qt.rgba(0,0,0,1)
z: 65535
}
}

View File

@ -1,19 +1,21 @@
import QtQuick import QtQuick
import QtQuick.Controls
Text { import Fluent
property int iconSource
property int iconSize: 20 Text {
property color iconColor: FluTheme.dark ? "#FFFFFF" : "#000000" property int iconSource
id:control property int iconSize: 20
font.family: font_loader.name property color iconColor: Theme.dark ? "#FFFFFF" : "#000000"
font.pixelSize: iconSize id:control
horizontalAlignment: Text.AlignHCenter font.family: font_loader.name
verticalAlignment: Text.AlignVCenter font.pixelSize: iconSize
color: iconColor horizontalAlignment: Text.AlignHCenter
text: (String.fromCharCode(iconSource).toString(16)) verticalAlignment: Text.AlignVCenter
opacity: iconSource>0 color: iconColor
FontLoader{ text: (String.fromCharCode(iconSource).toString(16))
id: font_loader opacity: iconSource>0
source: "qrc:/qt/qml/FluentUI/Font/FluentIcons.ttf" FontLoader{
} id: font_loader
} source: "qrc:/qt/qml/Fluent/resources/FluentIcons.ttf"
}
}

View File

@ -1,30 +1,129 @@
import QtQuick import QtQuick as Quick
import QtQuick.Controls import QtQuick.Controls as Controls
import QtQuick.Layouts
Button { import Fluent
property int iconSize: 20
property int iconSource Controls.Button {
property int radius:4 display: Controls.Button.IconOnly
property color color: { property int iconSize: 20
if(!enabled){ property int iconSource
return disableColor property bool disabled: false
} property int radius:4
if(pressed){ property string contentDescription: ""
return pressedColor property Quick.color hoverColor: Theme.itemHoverColor
} property Quick.color pressedColor: Theme.itemPressColor
return hovered ? hoverColor : normalColor property Quick.color normalColor: Theme.itemNormalColor
} property Quick.color disableColor: Theme.itemNormalColor
property color iconColor: { property Quick.Component iconDelegate: com_icon
if (FluTheme.dark) { property Quick.color color: {
if (!enabled) { if(!enabled){
return Qt.rgba(130 / 255, 130 / 255, 130 / 255, 1) return disableColor
} }
return Qt.rgba(1, 1, 1, 1) if(pressed){
} else { return pressedColor
if (!enabled) { }
return Qt.rgba(161 / 255, 161 / 255, 161 / 255, 1) return hovered ? hoverColor : normalColor
} }
return Qt.rgba(0, 0, 0, 1) property Quick.color iconColor: {
} if(Theme.dark){
} if(!enabled){
} return Qt.rgba(130/255,130/255,130/255,1)
}
return Qt.rgba(1,1,1,1)
}else{
if(!enabled){
return Qt.rgba(161/255,161/255,161/255,1)
}
return Qt.rgba(0,0,0,1)
}
}
property Quick.color textColor: Theme.fontPrimaryColor
Quick.Accessible.role: Quick.Accessible.Button
Quick.Accessible.name: control.text
Quick.Accessible.description: contentDescription
Quick.Accessible.onPressAction: control.clicked()
id:control
focusPolicy:Qt.TabFocus
padding: 0
verticalPadding: 8
horizontalPadding: 8
enabled: !disabled
font:TextStyle.Caption
background: Quick.Rectangle{
implicitWidth: 30
implicitHeight: 30
radius: control.radius
color:control.color
FocusRectangle{
visible: control.activeFocus
}
}
Quick.Component{
id:com_icon
Icon {
id:text_icon
font.pixelSize: iconSize
iconSize: control.iconSize
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
iconColor: control.iconColor
iconSource: control.iconSource
}
}
Quick.Component{
id:com_row
RowLayout{
Loader{
sourceComponent: iconDelegate
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
visible: display !== Button.TextOnly
}
Text{
text:control.text
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
visible: display !== Button.IconOnly
color: control.textColor
font: control.font
}
}
}
Quick.Component{
id:com_column
ColumnLayout{
Loader{
sourceComponent: iconDelegate
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
visible: display !== Button.TextOnly
}
Text{
text:control.text
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
visible: display !== Button.IconOnly
color: control.textColor
font: control.font
}
}
}
contentItem:Loader{
sourceComponent: {
if(display === Button.TextUnderIcon){
return com_column
}
return com_row
}
}
Tooltip{
id:tool_tip
visible: {
if(control.text === ""){
return false
}
if(control.display !== Button.IconOnly){
return false
}
return hovered
}
text:control.text
delay: 1000
}
}

48
Fluent/qml/Image.qml Normal file
View File

@ -0,0 +1,48 @@
import QtQuick
import QtQuick.Controls
import FluentUI
Image {
property string errorButtonText: qsTr("Reload")
property var clickErrorListener : function(){
image.source = ""
image.source = control.source
}
property Component errorItem : com_error
property Component loadingItem: com_loading
id: control
FluLoader{
anchors.fill: parent
sourceComponent: {
if(control.status === Image.Loading){
return com_loading
}else if(control.status == Image.Error){
return com_error
}else{
return undefined
}
}
}
Component{
id:com_loading
Rectangle{
color: FluTheme.itemHoverColor
FluProgressRing{
anchors.centerIn: parent
visible: control.status === Image.Loading
}
}
}
Component{
id:com_error
Rectangle{
color: FluTheme.itemHoverColor
FluFilledButton{
text: control.errorButtonText
anchors.centerIn: parent
visible: control.status === Image.Error
onClicked: clickErrorListener()
}
}
}
}

View File

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls as Controls
Controls.Button{
id:control
property string normalImage: ""
property string hoveredImage: ""
property string pushedImage: ""
background: Item{
implicitHeight: 12
implicitWidth: 12
BorderImage {
anchors.fill: parent
source: control.hovered ? (control.pressed ? control.pushedImage : control.hoveredImage ) : control.normalImage
}
}
}

View File

@ -1,245 +1,243 @@
import QtQuick as Quick import QtQuick as Quick
import QtQuick.Controls import QtQuick.Controls
import Fluent
Object {
Object { property var root
property var root property int layoutY: 75
property int layoutY: 75 id:control
id:control Object{
Object{ id:mcontrol
id:mcontrol property string const_success: "success"
property string const_success: "success" property string const_info: "info"
property string const_info: "info" property string const_warning: "warning"
property string const_warning: "warning" property string const_error: "error"
property string const_error: "error" property int maxWidth: 300
property int maxWidth: 300 property var screenLayout: null
property var screenLayout: null function create(type,text,duration,moremsg){
function create(type,text,duration,moremsg){ if(screenLayout){
if(screenLayout){ var last = screenLayout.getLastloader()
var last = screenLayout.getLastloader() if(last.type === type && last.text === text && moremsg === last.moremsg){
if(last.type === type && last.text === text && moremsg === last.moremsg){ last.duration = duration
last.duration = duration if (duration > 0) last.restart()
if (duration > 0) last.restart() return last
return last }
} }
} initScreenLayout()
initScreenLayout() return contentComponent.createObject(screenLayout,{type:type,text:text,duration:duration,moremsg:moremsg,})
return contentComponent.createObject(screenLayout,{type:type,text:text,duration:duration,moremsg:moremsg,}) }
} function createCustom(itemcomponent,duration){
function createCustom(itemcomponent,duration){ initScreenLayout()
initScreenLayout() if(itemcomponent){
if(itemcomponent){ return contentComponent.createObject(screenLayout,{itemcomponent:itemcomponent,duration:duration})
return contentComponent.createObject(screenLayout,{itemcomponent:itemcomponent,duration:duration}) }
} }
} function initScreenLayout(){
function initScreenLayout(){ if(screenLayout == null){
if(screenLayout == null){ screenLayout = screenlayoutComponent.createObject(root)
screenLayout = screenlayoutComponent.createObject(root) screenLayout.y = control.layoutY
screenLayout.y = control.layoutY screenLayout.z = 100000
screenLayout.z = 100000 }
} }
} Quick.Component{
Quick.Component { id:screenlayoutComponent
id:screenlayoutComponent Quick.Column{
Quick.Column{ parent: Overlay.overlay
parent: Overlay.overlay z:999
z:999 spacing: 20
spacing: 20 width: root.width
width: root.width move: Quick.Transition {
move: Quick.Transition { Quick.NumberAnimation {
Quick.NumberAnimation { properties: "y"
properties: "y" easing.type: Easing.OutCubic
easing.type: Easing.OutCubic duration: FluTheme.animationEnabled ? 333 : 0
duration: FluTheme.animationEnabled ? 333 : 0 }
} }
} onChildrenChanged: if(children.length === 0) destroy()
onChildrenChanged: if(children.length === 0) destroy() function getLastloader(){
function getLastloader(){ if(children.length > 0){
if(children.length > 0){ return children[children.length - 1]
return children[children.length - 1] }
} return null
return null }
} }
} }
} Quick.Component{
Quick.Component{ id:contentComponent
id:contentComponent Quick.Item{
Quick.Item{ id:content
id:content property int duration: 1500
property int duration: 1500 property var itemcomponent
property var itemcomponent property string type
property string type property string text
property string text property string moremsg
property string moremsg width: parent.width
width: parent.width height: loader.height
height: loader.height function close(){
function close(){ content.destroy()
content.destroy() }
} function restart(){
function restart(){ delayTimer.restart()
delayTimer.restart() }
} Quick.Timer {
Quick.Timer { id:delayTimer
id:delayTimer interval: duration
interval: duration running: duration > 0
running: duration > 0 repeat: duration > 0
repeat: duration > 0 onTriggered: content.close()
onTriggered: content.close() }
} Loader{
Quick.Loader{ id:loader
id:loader x:(parent.width - width) / 2
x:(parent.width - width) / 2 property var _super: content
property var _super: content scale: item ? 1 : 0
scale: item ? 1 : 0 asynchronous: true
asynchronous: true Quick.Behavior on scale {
Quick.Behavior on scale { enabled: FluTheme.animationEnabled
enabled: FluTheme.animationEnabled Quick.NumberAnimation {
Quick.NumberAnimation { easing.type: Easing.OutCubic
easing.type: Easing.OutCubic duration: 167
duration: 167 }
} }
} sourceComponent:itemcomponent ? itemcomponent : mcontrol.fluent_sytle
sourceComponent:itemcomponent ? itemcomponent : mcontrol.fluent_sytle }
Quick.Component.onDestruction: sourceComponent = undefined }
} }
} property Quick.Component fluent_sytle: Quick.Rectangle{
} width: rowlayout.width + (btn_close.visible ? 30 : 48)
property Quick.Component fluent_sytle: Quick.Rectangle{ height: rowlayout.height + 20
width: rowlayout.width + (btn_close.visible ? 30 : 48) color: {
height: rowlayout.height + 20 if(FluTheme.dark){
color: { switch(_super.type){
if(FluTheme.dark){ case mcontrol.const_success: return Qt.rgba(57/255,61/255,27/255,1)
switch(_super.type){ case mcontrol.const_warning: return Qt.rgba(67/255,53/255,25/255,1)
case mcontrol.const_success: return Qt.rgba(57/255,61/255,27/255,1) case mcontrol.const_info: return Qt.rgba(39/255,39/255,39/255,1)
case mcontrol.const_warning: return Qt.rgba(67/255,53/255,25/255,1) case mcontrol.const_error: return Qt.rgba(68/255,39/255,38/255,1)
case mcontrol.const_info: return Qt.rgba(39/255,39/255,39/255,1) }
case mcontrol.const_error: return Qt.rgba(68/255,39/255,38/255,1) return Qt.rgba(1,1,1,1)
} }else{
return Qt.rgba(1,1,1,1) switch(_super.type){
}else{ case mcontrol.const_success: return Qt.rgba(223/255,246/255,221/255,1)
switch(_super.type){ case mcontrol.const_warning: return Qt.rgba(255/255,244/255,206/255,1)
case mcontrol.const_success: return Qt.rgba(223/255,246/255,221/255,1) case mcontrol.const_info: return Qt.rgba(244/255,244/255,244/255,1)
case mcontrol.const_warning: return Qt.rgba(255/255,244/255,206/255,1) case mcontrol.const_error: return Qt.rgba(253/255,231/255,233/255,1)
case mcontrol.const_info: return Qt.rgba(244/255,244/255,244/255,1) }
case mcontrol.const_error: return Qt.rgba(253/255,231/255,233/255,1) return Qt.rgba(1,1,1,1)
} }
return Qt.rgba(1,1,1,1) }
} Shadow{
} radius: 4
Shadow { }
radius: 4 radius: 4
} border.width: 1
radius: 4 border.color: {
border.width: 1 if(FluTheme.dark){
border.color: { switch(_super.type){
if(FluTheme.dark){ case mcontrol.const_success: return Qt.rgba(56/255,61/255,27/255,1)
switch(_super.type){ case mcontrol.const_warning: return Qt.rgba(66/255,53/255,25/255,1)
case mcontrol.const_success: return Qt.rgba(56/255,61/255,27/255,1) case mcontrol.const_info: return Qt.rgba(38/255,39/255,39/255,1)
case mcontrol.const_warning: return Qt.rgba(66/255,53/255,25/255,1) case mcontrol.const_error: return Qt.rgba(67/255,39/255,38/255,1)
case mcontrol.const_info: return Qt.rgba(38/255,39/255,39/255,1) }
case mcontrol.const_error: return Qt.rgba(67/255,39/255,38/255,1) return Qt.rgba(1,1,1,1)
} }else{
return Qt.rgba(1,1,1,1) switch(_super.type){
}else{ case mcontrol.const_success: return Qt.rgba(210/255,232/255,208/255,1)
switch(_super.type){ case mcontrol.const_warning: return Qt.rgba(240/255,230/255,194/255,1)
case mcontrol.const_success: return Qt.rgba(210/255,232/255,208/255,1) case mcontrol.const_info: return Qt.rgba(230/255,230/255,230/255,1)
case mcontrol.const_warning: return Qt.rgba(240/255,230/255,194/255,1) case mcontrol.const_error: return Qt.rgba(238/255,217/255,219/255,1)
case mcontrol.const_info: return Qt.rgba(230/255,230/255,230/255,1) }
case mcontrol.const_error: return Qt.rgba(238/255,217/255,219/255,1) return Qt.rgba(1,1,1,1)
} }
return Qt.rgba(1,1,1,1) }
} Quick.Row{
} id:rowlayout
Quick.Row{ x:20
id:rowlayout y:(parent.height - height) / 2
x:20 spacing: 10
y:(parent.height - height) / 2 Icon{
spacing: 10 iconSource:{
Icon{ switch(_super.type){
iconSource:{ case mcontrol.const_success: return FluentIcons.CompletedSolid
switch(_super.type){ case mcontrol.const_warning: return FluentIcons.InfoSolid
case mcontrol.const_success: return FluentIcons.CompletedSolid case mcontrol.const_info: return FluentIcons.InfoSolid
case mcontrol.const_warning: return FluentIcons.InfoSolid case mcontrol.const_error: return FluentIcons.StatusErrorFull
case mcontrol.const_info: return FluentIcons.InfoSolid }FluentIcons.StatusErrorFull
case mcontrol.const_error: return FluentIcons.StatusErrorFull return FluentIcons.FA_info_circle
}FluentIcons.StatusErrorFull }
return FluentIcons.FA_info_circle iconSize:20
} iconColor: {
iconSize:20 if(FluTheme.dark){
iconColor: { switch(_super.type){
if(FluTheme.dark){ case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1)
switch(_super.type){ case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1)
case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1) case mcontrol.const_info: return FluTheme.primaryColor
case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1) case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1)
case mcontrol.const_info: return FluTheme.primaryColor }
case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1) return Qt.rgba(1,1,1,1)
} }else{
return Qt.rgba(1,1,1,1) switch(_super.type){
}else{ case mcontrol.const_success: return Qt.rgba(15/255,123/255,15/255,1)
switch(_super.type){ case mcontrol.const_warning: return Qt.rgba(157/255,93/255,0/255,1)
case mcontrol.const_success: return Qt.rgba(15/255,123/255,15/255,1) case mcontrol.const_info: return Qt.rgba(0/255,102/255,180/255,1)
case mcontrol.const_warning: return Qt.rgba(157/255,93/255,0/255,1) case mcontrol.const_error: return Qt.rgba(196/255,43/255,28/255,1)
case mcontrol.const_info: return Qt.rgba(0/255,102/255,180/255,1) }
case mcontrol.const_error: return Qt.rgba(196/255,43/255,28/255,1) return Qt.rgba(1,1,1,1)
} }
return Qt.rgba(1,1,1,1) }
} }
}
} Quick.Column{
spacing: 5
Quick.Column{ Text{
spacing: 5 text:_super.text
Text{ wrapMode: Text.WrapAnywhere
text:_super.text width: Math.min(implicitWidth,mcontrol.maxWidth)
wrapMode: Text.WrapAnywhere }
width: Math.min(implicitWidth,mcontrol.maxWidth) Text{
} text: _super.moremsg
Text{ visible: _super.moremsg
text: _super.moremsg wrapMode : Text.WrapAnywhere
visible: _super.moremsg textColor: FluColors.Grey120
wrapMode : Text.WrapAnywhere width: Math.min(implicitWidth,mcontrol.maxWidth)
textColor: FluColors.Grey120 }
width: Math.min(implicitWidth,mcontrol.maxWidth) }
}
} IconButton{
id:btn_close
IconButton{ iconSource: FluentIcons.ChromeClose
id:btn_close iconSize: 10
iconSource: FluentIcons.ChromeClose verticalPadding: 0
iconSize: 10 horizontalPadding: 0
verticalPadding: 0 width: 30
horizontalPadding: 0 height: 20
width: 30 visible: _super.duration<=0
height: 20 anchors.verticalCenter: parent.verticalCenter
visible: _super.duration<=0 iconColor: FluTheme.dark ? Qt.rgba(222/255,222/255,222/255,1) : Qt.rgba(97/255,97/255,97/255,1)
anchors.verticalCenter: parent.verticalCenter onClicked: _super.close()
iconColor: FluTheme.dark ? Qt.rgba(222/255,222/255,222/255,1) : Qt.rgba(97/255,97/255,97/255,1) }
onClicked: _super.close() }
} }
} }
} function showSuccess(text,duration=1000,moremsg){
} return mcontrol.create(mcontrol.const_success,text,duration,moremsg ? moremsg : "")
function showSuccess(text,duration=1000,moremsg){ }
return mcontrol.create(mcontrol.const_success,text,duration,moremsg ? moremsg : "") function showInfo(text,duration=1000,moremsg){
} return mcontrol.create(mcontrol.const_info,text,duration,moremsg ? moremsg : "")
function showInfo(text,duration=1000,moremsg){ }
return mcontrol.create(mcontrol.const_info,text,duration,moremsg ? moremsg : "") function showWarning(text,duration=1000,moremsg){
} return mcontrol.create(mcontrol.const_warning,text,duration,moremsg ? moremsg : "")
function showWarning(text,duration=1000,moremsg){ }
return mcontrol.create(mcontrol.const_warning,text,duration,moremsg ? moremsg : "") function showError(text,duration=1000,moremsg){
} return mcontrol.create(mcontrol.const_error,text,duration,moremsg ? moremsg : "")
function showError(text,duration=1000,moremsg){ }
return mcontrol.create(mcontrol.const_error,text,duration,moremsg ? moremsg : "") function showCustom(itemcomponent,duration=1000){
} return mcontrol.createCustom(itemcomponent,duration)
function showCustom(itemcomponent,duration=1000){ }
return mcontrol.createCustom(itemcomponent,duration) function clearAllInfo(){
} if(mcontrol.screenLayout != null) {
function clearAllInfo(){ mcontrol.screenLayout.destroy()
if(mcontrol.screenLayout != null) { mcontrol.screenLayout = null
mcontrol.screenLayout.destroy() }
mcontrol.screenLayout = null
} return true
}
return true }
}
}

5
Fluent/qml/Loader.qml Normal file
View File

@ -0,0 +1,5 @@
import QtQuick
Loader {
Component.onDestruction: sourceComponent = undefined
}

View File

@ -1,7 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls
QtObject {
id:root QtObject {
default property list<QtObject> children default property list<QtObject> children
id:control
} }

54
Fluent/qml/Popup.qml Normal file
View File

@ -0,0 +1,54 @@
import QtQuick as Quick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import QtQuick.Window
import Fluent
Controls.Popup {
id: control
padding: 0
modal:true
parent: Controls.Overlay.overlay
x: Math.round((d.parentWidth - width) / 2)
y: Math.round((d.parentHeight - height) / 2)
closePolicy: Popup.NoAutoClose
enter: Transition {
NumberAnimation {
property: "opacity"
duration: Theme.animationEnabled ? 83 : 0
from:0
to:1
}
}
height:Math.min(implicitHeight,d.parentHeight)
exit:Transition {
NumberAnimation {
property: "opacity"
duration: Theme.animationEnabled ? 83 : 0
from:1
to:0
}
}
background: Rectangle{
radius: [5,5,5,5]
color: Theme.dark ? Qt.rgba(43/255,43/255,43/255,1) : Qt.rgba(1,1,1,1)
Shadow{
radius: 5
}
}
QtObject{
id:d
property int parentHeight: {
if(control.parent){
return control.parent.height
}
return control.height
}
property int parentWidth: {
if(control.parent){
return control.parent.width
}
return control.width
}
}
}

View File

@ -0,0 +1,92 @@
import QtQuick as Quick
import QtQuick.Controls
import Fluent
ProgressBar{
property int duration: 2000
property real strokeWidth: 6
property bool progressVisible: false
property Quick.color color: FluTheme.primaryColor
property Quick.color backgroundColor : FluTheme.dark ? Qt.rgba(99/255,99/255,99/255,1) : Qt.rgba(214/255,214/255,214/255,1)
id:control
indeterminate : true
clip: true
background: Quick.Rectangle {
implicitWidth: 56
implicitHeight: 56
radius: control.width/2
color:"transparent"
border.color: control.backgroundColor
border.width: control.strokeWidth
}
onIndeterminateChanged:{
canvas.requestPaint()
}
Quick.QtObject{
id:d
property real _radius: control.width/2-control.strokeWidth/2
property real _progress: control.indeterminate ? 0.0 : control.visualPosition
on_ProgressChanged: {
canvas.requestPaint()
}
}
Quick.Connections{
target: FluTheme
function onDarkChanged(){
canvas.requestPaint()
}
}
contentItem: Quick.Item {
id:layout_item
Quick.Canvas {
id:canvas
anchors.fill: parent
antialiasing: true
renderTarget: Canvas.Image
property real startAngle: 0
property real sweepAngle: 0
Quick.SequentialAnimation on startAngle {
loops: Animation.Infinite
running: control.visible && control.indeterminate
Quick.PropertyAnimation { from: 0; to: 450; duration: control.duration/2 }
Quick.PropertyAnimation { from: 450; to: 1080; duration: control.duration/2 }
}
Quick.SequentialAnimation on sweepAngle {
loops: Animation.Infinite
running: control.visible && control.indeterminate
Quick.PropertyAnimation { from: 0; to: 180; duration: control.duration/2 }
Quick.PropertyAnimation { from: 180; to: 0; duration: control.duration/2 }
}
onStartAngleChanged: {
requestPaint()
}
onPaint: {
var ctx = canvas.getContext("2d")
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.save()
ctx.lineWidth = control.strokeWidth
ctx.strokeStyle = control.color
ctx.lineCap = "round"
ctx.beginPath()
if(control.indeterminate){
ctx.arc(width/2, height/2, d._radius , Math.PI * (startAngle - 90) / 180, Math.PI * (startAngle - 90 + sweepAngle) / 180)
}else{
ctx.arc(width/2, height/2, d._radius , -0.5 * Math.PI , -0.5 * Math.PI + d._progress * 2 * Math.PI)
}
ctx.stroke()
ctx.closePath()
ctx.restore()
}
}
}
Text{
text:(control.visualPosition * 100).toFixed(0) + "%"
visible: {
if(control.indeterminate){
return false
}
return control.progressVisible
}
anchors.centerIn: parent
}
}

View File

@ -1,12 +1,71 @@
pragma Singleton pragma Singleton
import QtQuick
import QtQml
QtObject { QtObject {
property var windows: [] property var routes : ({})
function addWindow(window){ property var windows: []
if(!window.transientParent){ function addWindow(window){
windows.push(window) if(!window.transientParent){
} windows.push(window)
} }
} }
function removeWindow(win) {
if(!win.transientParent){
var index = windows.indexOf(win)
if (index !== -1) {
windows.splice(index, 1)
win.deleteLater()
}
}
}
function exit(retCode){
for(var i =0 ;i< windows.length; i++){
var win = windows[i]
win.deleteLater()
}
windows = []
Qt.exit(retCode)
}
function navigate(route,argument={},windowRegister = undefined){
if(!routes.hasOwnProperty(route)){
console.error("Not Found Route",route)
return
}
var windowComponent = Qt.createComponent(routes[route])
if (windowComponent.status !== Component.Ready) {
console.error(windowComponent.errorString())
return
}
var properties = {}
properties._route = route
if(windowRegister){
properties._windowRegister = windowRegister
}
properties.argument = argument
var win = undefined
for(var i =0 ;i< windows.length; i++){
var item = windows[i]
if(route === item._route){
win = item
break
}
}
if(win){
var launchMode = win.launchMode
if(launchMode === 1){
win.argument = argument
win.show()
win.raise()
win.requestActivate()
return
}else if(launchMode === 2){
win.close()
}
}
win = windowComponent.createObject(null,properties)
if(windowRegister){
windowRegister._to = win
}
}
}

187
Fluent/qml/ScrollBar.qml Normal file
View File

@ -0,0 +1,187 @@
import QtQuick
import QtQuick.Controls.impl
import QtQuick.Templates as T
import Fluent
T.ScrollBar {
id: control
property color color : Theme.dark ? Qt.rgba(159/255,159/255,159/255,1) : Qt.rgba(138/255,138/255,138/255,1)
property color pressedColor: Theme.dark ? Qt.darker(color,1.2) : Qt.lighter(color,1.2)
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
visible: control.policy !== T.ScrollBar.AlwaysOff
minimumSize: Math.max(orientation === Qt.Horizontal ? height / width : width / height,0.3)
QtObject{
id:d
property int minLine : 2
property int maxLine : 6
}
z: horizontal? 10 : 20
verticalPadding : vertical ? 15 : 3
horizontalPadding : horizontal ? 15 : 3
background: Rectangle{
id:back_rect
radius: 5
color:Theme.dark ? Qt.rgba(44/255,44/255,44/255,1) : Qt.rgba(255/255,255/255,255/255,1)
opacity:{
if(vertical){
return d.maxLine === Number(rect_bar.width)
}
return d.maxLine === Number(rect_bar.height)
}
Behavior on opacity {
NumberAnimation{
duration: 50
}
}
}
IconButton{
width: 12
height: 12
iconSize: 8
verticalPadding: 0
horizontalPadding: 0
visible: control.horizontal
opacity: back_rect.opacity
anchors{
left: parent.left
leftMargin: 2
verticalCenter: parent.verticalCenter
}
iconColor: control.color
iconSource: Icons.CaretLeftSolid8
onClicked: {
control.decrease()
}
}
IconButton{
width: 12
height: 12
iconSize: 8
verticalPadding: 0
horizontalPadding: 0
iconColor: control.color
opacity: back_rect.opacity
anchors{
right: parent.right
rightMargin: 2
verticalCenter: parent.verticalCenter
}
visible: control.horizontal
iconSource: Icons.CaretRightSolid8
onClicked: {
control.increase()
}
}
IconButton{
width: 12
height: 12
iconSize: 8
verticalPadding: 0
horizontalPadding: 0
iconColor: control.color
opacity: back_rect.opacity
anchors{
top: parent.top
topMargin: 2
horizontalCenter: parent.horizontalCenter
}
visible: control.vertical
iconSource: Icons.CaretUpSolid8
onClicked: {
control.decrease()
}
}
IconButton{
width: 12
height: 12
iconSize: 8
verticalPadding: 0
horizontalPadding: 0
iconColor: control.color
opacity: back_rect.opacity
anchors{
bottom: parent.bottom
bottomMargin: 2
horizontalCenter: parent.horizontalCenter
}
visible: control.vertical
iconSource: Icons.CaretDownSolid8
onClicked: {
control.increase()
}
}
contentItem: Item {
property bool collapsed: (control.policy === T.ScrollBar.AlwaysOn || (control.active && control.size < 1.0))
implicitWidth: control.interactive ? d.maxLine : d.minLine
implicitHeight: control.interactive ? d.maxLine : d.minLine
Rectangle{
id:rect_bar
width: vertical ? d.minLine : parent.width
height: horizontal ? d.minLine : parent.height
color:{
if(control.pressed){
return control.pressedColor
}
return control .color
}
anchors{
right: vertical ? parent.right : undefined
bottom: horizontal ? parent.bottom : undefined
}
radius: width / 2
visible: control.size < 1.0
}
states: [
State{
name:"show"
when: contentItem.collapsed
PropertyChanges {
target: rect_bar
width: vertical ? d.maxLine : parent.width
height: horizontal ? d.maxLine : parent.height
}
}
,State{
name:"hide"
when: !contentItem.collapsed
PropertyChanges {
target: rect_bar
width: vertical ? d.minLine : parent.width
height: horizontal ? d.minLine : parent.height
}
}
]
transitions:[
Transition {
to: "hide"
SequentialAnimation {
PauseAnimation { duration: 450 }
NumberAnimation {
target: rect_bar
properties: vertical ? "width" : "height"
duration: 167
easing.type: Easing.OutCubic
}
}
}
,Transition {
to: "show"
SequentialAnimation {
PauseAnimation { duration: 150 }
NumberAnimation {
target: rect_bar
properties: vertical ? "width" : "height"
duration: 167
easing.type: Easing.OutCubic
}
}
}
]
}
}

View File

@ -1,14 +1,15 @@
import QtQuick import QtQuick as Quick
import Fluent
Item { Quick.Item {
property color color: FluTheme.dark ? "#000000" : "#999999" property Quick.color color: Theme.dark ? "#000000" : "#999999"
property int elevation: 5 property int elevation: 5
property int radius: 4 property int radius: 4
id:control id:control
anchors.fill: parent anchors.fill: parent
Repeater{ Quick.Repeater{
model: elevation model: elevation
Rectangle{ Quick.Rectangle{
anchors.fill: parent anchors.fill: parent
color: "#00000000" color: "#00000000"
opacity: 0.01 * (elevation-index+1) opacity: 0.01 * (elevation-index+1)

View File

@ -1,11 +1,10 @@
import QtQuick as Quick import QtQuick as Quick
import Fluent import Fluent
Quick.Text { Quick.Text {
property Quick.color textColor: FluTheme.fontPrimaryColor property Quick.color textColor: Theme.fontPrimaryColor
id:text id:text
color: textColor color: textColor
renderType: FluTheme.nativeText ? Text.NativeRendering : Text.QtRendering renderType: Theme.nativeText ? Text.NativeRendering : Text.QtRendering
font: FluTextStyle.Body font: TextStyle.Body
} }

31
Fluent/qml/Tooltip.qml Normal file
View File

@ -0,0 +1,31 @@
import QtQuick as Quick
import QtQuick.Controls.impl
import QtQuick.Templates as T
import Fluent
T.ToolTip {
id: control
x: parent ? (parent.width - implicitWidth) / 2 : 0
y: -implicitHeight - 3
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
contentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding)
margins: 6
padding: 6
font: TextStyle.Body
closePolicy: T.Popup.CloseOnEscape | T.Popup.CloseOnPressOutsideParent | T.Popup.CloseOnReleaseOutsideParent
contentItem: Text {
text: control.text
font: control.font
wrapMode: Text.Wrap
}
background: Quick.Rectangle {
color: Theme.dark ? Qt.rgba(50/255,49/255,48/255,1) : Qt.rgba(1,1,1,1)
radius: 3
Shadow{
radius: 3
}
}
}

View File

@ -1,186 +1,387 @@
import QtQuick as Quick import QtQuick as Quick
import Fluent import QtQuick.Controls
import QtQuick.Layouts
Quick.Window { import Fluent
id: root
property string windowIcon: App.windowIcon Quick.Window {
property bool showStayTop: false id: window
property bool showMaximize: true default property alias contentData : layout_content.data
property bool showMinimize: true property string windowIcon: App.windowIcon
property bool showClose: true property int launchMode: WindowType.Standard
property bool showDark: false property var argument:({})
property bool fixSize: false property var background : com_background
property bool stayTop: false property bool fixSize: false
property int __margins: 0 property Quick.Component loadingItem: com_loading
property var background : com_background property bool fitsAppBarWindows: false
property Quick.color backgroundColor: { property var tintOpacity: Theme.dark ? 0.80 : 0.75
if(active){ property int blurRadius: 60
return Theme.windowActiveBackgroundColor property alias effect: frameless.effect
} readonly property alias effective: frameless.effective
return Theme.windowBackgroundColor readonly property var availableEffects: frameless.availableEffects
} property Quick.Item appBar: AppBar {
property Quick.Item appBar: AppBar { title: window.title
title: root.title height: 30
height: 30 showDark: window.showDark
showDark: root.showDark showClose: window.showClose
showClose: root.showClose showMinimize: window.showMinimize
showMinimize: root.showMinimize showMaximize: window.showMaximize
showMaximize: root.showMaximize showStayTop: window.showStayTop
showStayTop: root.showStayTop icon: window.windowIcon
icon: root.windowIcon }
} property Quick.color backgroundColor: {
Frameless { if(frameless.effective && active){
id: frameless var backcolor
appBar: root.appBar if(frameless.effect==="dwm-blur"){
maximizeButton: appBar.buttonMaximize backcolor = Utilities.withOpacity(Theme.windowActiveBackgroundColor, window.tintOpacity)
fixSize: root.fixSize }else{
topmost: root.stayTop backcolor = "transparent"
disabled: App.useSystemAppBar }
Quick.Component.onCompleted: { return backcolor
frameless.setHitTestVisible(appBar.layoutMacosButtons) }
frameless.setHitTestVisible(appBar.layoutStandardbuttons) if(active){
} return Theme.windowActiveBackgroundColor
Quick.Component.onDestruction: { }
frameless.onDestruction() return Theme.windowBackgroundColor
} }
} property bool stayTop: false
Quick.Component.onCompleted: { property bool showDark: false
Router.addWindow(root) property bool showClose: true
} property bool showMinimize: true
property bool showMaximize: true
Quick.Component { property bool showStayTop: false
id:com_app_bar property bool autoMaximize: false
Quick.Item{ property bool autoVisible: true
data: root.appBar property bool autoCenter: true
Quick.Component.onCompleted: { property bool autoDestroy: true
root.appBar.width = Qt.binding(function(){ property bool useSystemAppBar
return this.parent.width property int __margins: 0
}) property Quick.color resizeBorderColor: {
} if(window.active){
} return Theme.dark ? Qt.rgba(51/255,51/255,51/255,1) : Qt.rgba(110/255,110/255,110/255,1)
} }
return Theme.dark ? Qt.rgba(61/255,61/255,61/255,1) : Qt.rgba(167/255,167/255,167/255,1)
}
Quick.Item{ property int resizeBorderWidth: 1
id: layout_container property var closeListener: function(event){
anchors.fill: parent if(autoDestroy){
anchors.margins: root.__margins Router.removeWindow(window)
Quick.Loader{ }else{
anchors.fill: parent window.visibility = Window.Hidden
sourceComponent: background event.accepted = false
Quick.Component.onDestruction: sourceComponent = undefined }
} }
Quick.Loader{ signal initArgument(var argument)
id:loader_app_bar signal lazyLoad()
anchors { property var _windowRegister
top: parent.top property string _route
left: parent.left property bool _hideShadow: false
right: parent.right color: Utilities.isSoftware() ? window.backgroundColor : "transparent"
} Quick.Component.onCompleted: {
height: { Router.addWindow(window)
if(root.useSystemAppBar){ useSystemAppBar = App.useSystemAppBar
return 0 if(!useSystemAppBar && autoCenter){
} moveWindowToDesktopCenter()
return root.fitsAppBarWindows ? 0 : root.appBar.height }
} fixWindowSize()
sourceComponent: root.useSystemAppBar ? undefined : com_app_bar initArgument(argument)
Quick.Component.onDestruction: sourceComponent = undefined if(window.autoVisible){
} if(window.autoMaximize){
Quick.Item{ window.visibility = Window.Maximized
id:layout_content }else{
anchors{ window.show()
top: loader_app_bar.bottom }
left: parent.left }
right: parent.right }
bottom: parent.bottom onVisibleChanged: {
} if(visible && d.isLazyInit){
clip: true window.lazyLoad()
} d.isLazyInit = false
Quick.Loader{ }
property string loadingText }
property bool cancel: false Quick.QtObject{
id:loader_loading id:d
anchors.fill: parent property bool isLazyInit: true
Quick.Component.onDestruction: sourceComponent = undefined }
} Quick.Connections{
InfoBar{ target: window
id:info_bar function onClosing(event){closeListener(event)}
root: layout_container }
} Frameless{
id: frameless
Quick.Loader{ appBar: window.appBar
id:loader_border maximizeButton: appBar.buttonMaximize
anchors.fill: parent fixSize: window.fixSize
sourceComponent: { topmost: window.stayTop
if(root.useSystemAppBar || Utilities.isWin() || root.visibility === Window.Maximized || root.visibility === Window.FullScreen){ disabled: App.useSystemAppBar
return undefined Quick.Component.onCompleted: {
} frameless.setHitTestVisible(appBar.layoutMacosButtons)
return com_border frameless.setHitTestVisible(appBar.layoutStandardbuttons)
} }
Quick.Component.onDestruction: sourceComponent = undefined Quick.Component.onDestruction: {
} frameless.onDestruction()
} }
onEffectiveChanged: {
Quick.Component { if(effective){
id:com_background Theme.blurBehindWindowEnabled = false
Quick.Item{ }
Rectangle{ }
anchors.fill: parent }
color: root.backgroundColor Quick.Component{
} id:com_background
Quick.Image{ Quick.Item{
id:img_back Quick.Rectangle{
visible: false anchors.fill: parent
cache: false color: window.backgroundColor
fillMode: Quick.Image.PreserveAspectCrop }
asynchronous: true Quick.Image{
Quick.Component.onCompleted: { id:img_back
img_back.updateLayout() visible: false
source = Utilities.getUrlByFilePath(Theme.desktopImagePath) cache: false
} fillMode: Quick.Image.PreserveAspectCrop
Quick.Connections{ asynchronous: true
target: root Quick.Component.onCompleted: {
function onScreenChanged(){ img_back.updateLayout()
img_back.updateLayout() source = Utilities.getUrlByFilePath(Theme.desktopImagePath)
} }
} Quick.Connections{
function updateLayout(){ target: window
var geometry = Utilities.desktopAvailableGeometry(root) function onScreenChanged(){
img_back.width = geometry.width img_back.updateLayout()
img_back.height = geometry.height }
img_back.sourceSize = Qt.size(img_back.width,img_back.height) }
} function updateLayout(){
Quick.Connections{ var geometry = Utilities.desktopAvailableGeometry(window)
target: Theme img_back.width = geometry.width
function onDesktopImagePathChanged(){ img_back.height = geometry.height
timer_update_image.restart() img_back.sourceSize = Qt.size(img_back.width,img_back.height)
} }
function onBlurBehindWindowEnabledChanged(){ Quick.Connections{
if(Theme.blurBehindWindowEnabled){ target: Theme
img_back.source = Utilities.getUrlByFilePath(Theme.desktopImagePath) function onDesktopImagePathChanged(){
}else{ timer_update_image.restart()
img_back.source = "" }
} function onBlurBehindWindowEnabledChanged(){
} if(Theme.blurBehindWindowEnabled){
} img_back.source = Utilities.getUrlByFilePath(Theme.desktopImagePath)
Quick.Timer{ }else{
id:timer_update_image img_back.source = ""
interval: 150 }
onTriggered: { }
img_back.source = "" }
img_back.source = Utilities.getUrlByFilePath(Theme.desktopImagePath) Quick.Timer{
} id:timer_update_image
} interval: 150
} onTriggered: {
Acrylic{ img_back.source = ""
anchors.fill: parent img_back.source = Utilities.getUrlByFilePath(Theme.desktopImagePath)
target: img_back }
tintOpacity: Theme.dark ? 0.80 : 0.75 }
blurRadius: 64 }
visible: root.active && Theme.blurBehindWindowEnabled Acrylic{
tintColor: Theme.dark ? Qt.rgba(0, 0, 0, 1) : Qt.rgba(1, 1, 1, 1) anchors.fill: parent
targetRect: Qt.rect(root.x-root.screen.virtualX,root.y-root.screen.virtualY,root.width,root.height) target: img_back
} tintOpacity: window.tintOpacity
} blurRadius: window.blurRadius
} visible: window.active && Theme.blurBehindWindowEnabled
} tintColor: Theme.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)
}
}
}
Quick.Component{
id:com_app_bar
Quick.Item{
data: window.appBar
Quick.Component.onCompleted: {
window.appBar.width = Qt.binding(function(){
return this.parent.width
})
}
}
}
Quick.Component{
id:com_loading
Popup{
id:popup_loading
focus: true
width: window.width
height: window.height
anchors.centerIn: Overlay.overlay
closePolicy: {
if(cancel){
return Popup.CloseOnEscape | Popup.CloseOnPressOutside
}
return Popup.NoAutoClose
}
Overlay.modal: Quick.Item {}
onVisibleChanged: {
if(!visible){
loader_loading.sourceComponent = undefined
}
}
padding: 0
opacity: 0
visible:true
Quick.Behavior on opacity {
Quick.SequentialAnimation {
Quick.PauseAnimation {
duration: 83
}
Quick.NumberAnimation{
duration: 167
}
}
}
Quick.Component.onCompleted: {
opacity = 1
}
background: Quick.Rectangle{
color:"#44000000"
}
contentItem: Quick.Item{
Quick.MouseArea{
anchors.fill: parent
onClicked: {
if (cancel){
popup_loading.visible = false
}
}
}
ColumnLayout{
spacing: 8
anchors.centerIn: parent
ProgressRing{
Layout.alignment: Qt.AlignHCenter
}
Text{
text:loadingText
Layout.alignment: Qt.AlignHCenter
}
}
}
}
}
Quick.Component{
id:com_border
Quick.Rectangle{
color:"transparent"
border.width: window.resizeBorderWidth
border.color: window.resizeBorderColor
}
}
Quick.Item{
id: layout_container
anchors.fill: parent
anchors.margins: window.__margins
Loader{
anchors.fill: parent
sourceComponent: background
}
Loader{
id:loader_app_bar
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: {
if(window.useSystemAppBar){
return 0
}
return window.fitsAppBarWindows ? 0 : window.appBar.height
}
sourceComponent: window.useSystemAppBar ? undefined : com_app_bar
}
Quick.Item{
id: layout_content
anchors{
top: loader_app_bar.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
}
clip: true
}
Loader{
property string loadingText
property bool cancel: false
id:loader_loading
anchors.fill: parent
}
InfoBar{
id:info_bar
root: layout_container
}
Loader{
id:loader_border
anchors.fill: parent
sourceComponent: {
if(window.useSystemAppBar || Utilities.isWin() || window.visibility === Window.Maximized || window.visibility === Window.FullScreen){
return undefined
}
return com_border
}
}
}
function hideLoading(){
loader_loading.sourceComponent = undefined
}
function showSuccess(text,duration,moremsg){
return info_bar.showSuccess(text,duration,moremsg)
}
function showInfo(text,duration,moremsg){
return info_bar.showInfo(text,duration,moremsg)
}
function showWarning(text,duration,moremsg){
return info_bar.showWarning(text,duration,moremsg)
}
function showError(text,duration,moremsg){
return info_bar.showError(text,duration,moremsg)
}
function clearAllInfo(){
return info_bar.clearAllInfo()
}
function moveWindowToDesktopCenter(){
var availableGeometry = Utilities.desktopAvailableGeometry(window)
window.setGeometry((availableGeometry.width-window.width)/2+Quick.Screen.virtualX,(availableGeometry.height-window.height)/2+Quick.Screen.virtualY,window.width,window.height)
}
function fixWindowSize(){
if(fixSize){
window.maximumWidth = window.width
window.maximumHeight = window.height
window.minimumWidth = window.width
window.minimumHeight = window.height
}
}
function setResult(data){
if(_windowRegister){
_windowRegister.setResult(data)
}
}
function showMaximized(){
frameless.showMaximized()
}
function showMinimized(){
frameless.showMinimized()
}
function showNormal(){
frameless.showNormal()
}
function showLoading(text = "",cancel = true){
if(text===""){
text = qsTr("Loading...")
}
loader_loading.loadingText = text
loader_loading.cancel = cancel
loader_loading.sourceComponent = com_loading
}
function setHitTestVisible(val){
frameless.setHitTestVisible(val)
}
function deleteLater(){
Utilities.deleteLater(window)
}
function containerItem(){
return layout_container
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB