diff --git a/Examples/FluentWindow/main.cpp b/Examples/FluentWindow/main.cpp index 3f98da5..267e69b 100644 --- a/Examples/FluentWindow/main.cpp +++ b/Examples/FluentWindow/main.cpp @@ -2,10 +2,12 @@ #include "Rectangle.h" #include #include +#include int main(int argc, char *argv[]) { LOG(info) << "app start..."; QGuiApplication app(argc, argv); + QQuickStyle::setStyle("Basic"); QQmlApplicationEngine engine; QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, diff --git a/Fluent/AccentColor.cpp b/Fluent/AccentColor.cpp new file mode 100644 index 0000000..ba1f472 --- /dev/null +++ b/Fluent/AccentColor.cpp @@ -0,0 +1,4 @@ +#include "AccentColor.h" + +AccentColor::AccentColor(QObject *parent) : QObject{parent} { +} diff --git a/Fluent/AccentColor.h b/Fluent/AccentColor.h new file mode 100644 index 0000000..80aa8c8 --- /dev/null +++ b/Fluent/AccentColor.h @@ -0,0 +1,22 @@ +#ifndef __ACCENTCOLOR_H__ +#define __ACCENTCOLOR_H__ + +#include "Utilities.h" +#include + +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__ diff --git a/Fluent/CMakeLists.txt b/Fluent/CMakeLists.txt index 964319a..d3daa1e 100644 --- a/Fluent/CMakeLists.txt +++ b/Fluent/CMakeLists.txt @@ -1,5 +1,5 @@ -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Gui Quick) -find_package(Qt${QT_VERSION_MAJOR} 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 QuickControls2) qt_standard_project_setup(REQUIRES 6.5) @@ -16,25 +16,41 @@ qt6_add_qml_module(Fluent URI Fluent VERSION 1.0 SOURCES + AccentColor.h AccentColor.cpp App.h App.cpp + CircularReveal.h CircularReveal.cpp + Colors.h Colors.cpp Frameless.h Frameless.cpp Icons.h Rectangle.h Rectangle.cpp + TextStyle.h TextStyle.cpp Theme.h Theme.cpp Utilities.h Utilities.cpp QML_FILES qml/Acrylic.qml qml/AppBar.qml + qml/Button.qml + qml/ContentDialog.qml + qml/ControlBackground.qml + qml/FilledButton.qml + qml/FocusRectangle.qml qml/Icon.qml qml/IconButton.qml + qml/ImageButton.qml qml/InfoBar.qml + qml/Loader.qml qml/Object.qml + qml/Popup.qml + qml/ProgressRing.qml qml/Router.qml + qml/ScrollBar.qml qml/Shadow.qml qml/Text.qml + qml/Tooltip.qml qml/Window.qml RESOURCES resources/noise.png + resources/FluentIcons.ttf ) target_include_directories(Fluent @@ -44,4 +60,5 @@ target_include_directories(Fluent target_link_libraries(Fluent PUBLIC Qt${QT_VERSION_MAJOR}::Gui PRIVATE Qt${QT_VERSION_MAJOR}::Quick + INTERFACE Qt${QT_VERSION_MAJOR}::QuickControls2 ) diff --git a/Fluent/CircularReveal.cpp b/Fluent/CircularReveal.cpp new file mode 100644 index 0000000..13c1184 --- /dev/null +++ b/Fluent/CircularReveal.cpp @@ -0,0 +1,66 @@ +#include "CircularReveal.h" +#include +#include +#include + +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(width()), static_cast(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 ¢er, 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(); +} diff --git a/Fluent/CircularReveal.h b/Fluent/CircularReveal.h new file mode 100644 index 0000000..1f6c35b --- /dev/null +++ b/Fluent/CircularReveal.h @@ -0,0 +1,31 @@ +#ifndef __CIRCULARREVEAL_H__ +#define __CIRCULARREVEAL_H__ + +#include "Utilities.h" +#include +#include +#include +#include + +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 ¢er, int radius); + Q_SIGNAL void imageChanged(); + Q_SIGNAL void animationFinished(); + Q_SLOT void handleGrabResult(); + +private: + QPropertyAnimation *_anim = nullptr; + QImage _source; + QPoint _center; + QSharedPointer _grabResult; +}; + +#endif // __CIRCULARREVEAL_H__ diff --git a/Fluent/Colors.cpp b/Fluent/Colors.cpp new file mode 100644 index 0000000..edf32f9 --- /dev/null +++ b/Fluent/Colors.cpp @@ -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; +} diff --git a/Fluent/Colors.h b/Fluent/Colors.h new file mode 100644 index 0000000..6e07eeb --- /dev/null +++ b/Fluent/Colors.h @@ -0,0 +1,56 @@ +#ifndef __COLORS_H__ +#define __COLORS_H__ + +#include "AccentColor.h" +#include "Utilities.h" +#include + +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__ diff --git a/Fluent/Frameless.cpp b/Fluent/Frameless.cpp index 3994172..7632e8d 100644 --- a/Fluent/Frameless.cpp +++ b/Fluent/Frameless.cpp @@ -1,4 +1,5 @@ #include "Frameless.h" +#include "Theme.h" #include "Utilities.h" #include #include @@ -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(::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) { auto window = item->window(); if ((window == nullptr) || !item || !item->isVisible()) { @@ -32,8 +148,189 @@ static bool containsCursorToItem(QQuickItem *item) { return false; } +static inline bool initializeFunctionPointers() { + HMODULE module = LoadLibraryW(L"dwmapi.dll"); + if (module) { + if (!pDwmSetWindowAttribute) { + pDwmSetWindowAttribute = + reinterpret_cast(GetProcAddress(module, "DwmSetWindowAttribute")); + if (!pDwmSetWindowAttribute) { + return false; + } + } + if (!pDwmExtendFrameIntoClientArea) { + pDwmExtendFrameIntoClientArea = reinterpret_cast( + GetProcAddress(module, "DwmExtendFrameIntoClientArea")); + if (!pDwmExtendFrameIntoClientArea) { + return false; + } + } + if (!pDwmIsCompositionEnabled) { + pDwmIsCompositionEnabled = + reinterpret_cast(::GetProcAddress(module, "DwmIsCompositionEnabled")); + if (!pDwmIsCompositionEnabled) { + return false; + } + } + if (!pDwmEnableBlurBehindWindow) { + pDwmEnableBlurBehindWindow = + reinterpret_cast(GetProcAddress(module, "DwmEnableBlurBehindWindow")); + if (!pDwmEnableBlurBehindWindow) { + return false; + } + } + if (!pSetWindowCompositionAttribute) { + HMODULE user32 = LoadLibraryW(L"user32.dll"); + if (!user32) { + return false; + } + pSetWindowCompositionAttribute = reinterpret_cast( + 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} { m_isWindows11OrGreater = Utilities::instance()->isWindows11OrGreater(); + m_effect = "normal"; } QQuickItem *Frameless::appBar() const { @@ -119,12 +416,36 @@ void Frameless::setHitTestVisible(QQuickItem *item) { } } +void Frameless::showMaximized() { +#ifdef Q_OS_WIN + HWND hwnd = reinterpret_cast(window()->winId()); + ::ShowWindow(hwnd, 3); +#else + window()->setVisibility(QQuickWindow::Maximized); +#endif +} + +void Frameless::showMinimized() { +#ifdef Q_OS_WIN + HWND hwnd = reinterpret_cast(window()->winId()); + ::ShowWindow(hwnd, 2); +#else + window()->setVisibility(QQuickWindow::Minimized); +#endif +} + +void Frameless::showNormal() { + window()->setVisibility(QQuickWindow::Windowed); +} + void Frameless::onDestruction() { QGuiApplication::instance()->removeNativeEventFilter(this); } void Frameless::componentComplete() { - if (m_disabled) return; + if (m_disabled) { + return; + } int w = window()->width(); int h = window()->height(); m_current = window()->winId(); @@ -150,8 +471,12 @@ void Frameless::componentComplete() { #endif HWND hwnd = reinterpret_cast(window()->winId()); DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE); +#if (QT_VERSION == QT_VERSION_CHECK(6, 7, 2)) + style &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); +#endif if (m_fixSize) { ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION); + ; for (int i = 0; i <= QGuiApplication::screens().count() - 1; ++i) { connect(QGuiApplication::screens().at(i), &QScreen::logicalDotsPerInchChanged, this, [=] { SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, @@ -171,6 +496,42 @@ void Frameless::componentComplete() { if (!window()->property("_hideShadow").toBool()) { setShadow(hwnd); } + if (isWin11OrGreater()) { + availableEffects({"mica", "mica-alt", "acrylic", "dwm-blur", "normal"}); + } else if (isWin7Only()) { + availableEffects({"dwm-blur", "normal"}); + } + if (!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 auto appBarHeight = m_appBar->height(); h = qRound(h + appBarHeight); diff --git a/Fluent/Frameless.h b/Fluent/Frameless.h index 9b25041..856eb1d 100644 --- a/Fluent/Frameless.h +++ b/Fluent/Frameless.h @@ -1,12 +1,16 @@ #ifndef FRAMELESS_H #define FRAMELESS_H +#include "Utilities.h" #include #include class Frameless : public QQuickItem, QAbstractNativeEventFilter { Q_OBJECT 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 *maximizeButton READ maximizeButton WRITE setMaximizeButton NOTIFY maximizeButtonChanged) Q_PROPERTY(QQuickItem *minimizedButton READ minimizedButton WRITE setMinimizedButton NOTIFY minimizedButtonChanged) @@ -39,6 +43,10 @@ public: bool disabled() const; 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 onDestruction(); void componentComplete() final; @@ -77,6 +85,7 @@ private: int m_margins = 8; QList> m_hitTestList; bool m_isWindows11OrGreater = false; + QString m_currentEffect; }; #endif // FRAMELESS_H diff --git a/Fluent/TextStyle.cpp b/Fluent/TextStyle.cpp new file mode 100644 index 0000000..44c8c2a --- /dev/null +++ b/Fluent/TextStyle.cpp @@ -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); +} diff --git a/Fluent/TextStyle.h b/Fluent/TextStyle.h new file mode 100644 index 0000000..3b4aa7b --- /dev/null +++ b/Fluent/TextStyle.h @@ -0,0 +1,25 @@ +#ifndef __TEXTSTYLE_H__ +#define __TEXTSTYLE_H__ + +#include "Utilities.h" +#include +#include + +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__ diff --git a/Fluent/Theme.cpp b/Fluent/Theme.cpp index 8787632..4ae4891 100644 --- a/Fluent/Theme.cpp +++ b/Fluent/Theme.cpp @@ -1,6 +1,56 @@ #include "Theme.h" +#include "Colors.h" +#include +#include +#include + +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} { + 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 { @@ -79,3 +129,53 @@ void Theme::setBlurBehindWindowEnabled(bool enabled) { 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(); +} diff --git a/Fluent/Theme.h b/Fluent/Theme.h index 36131f3..88c4c8f 100644 --- a/Fluent/Theme.h +++ b/Fluent/Theme.h @@ -1,7 +1,11 @@ #ifndef THEME_H #define THEME_H +#include "AccentColor.h" +#include "Utilities.h" #include +#include +#include #include #include @@ -9,7 +13,21 @@ class Theme : public QObject { Q_OBJECT QML_ELEMENT 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_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 itemHoverColor READ itemHoverColor WRITE setItemHoverColor NOTIFY itemHoverColorChanged) @@ -23,7 +41,9 @@ class Theme : public QObject { blurBehindWindowEnabledChanged) public: - Theme(QObject *parent = nullptr); + static Theme *instance(); + static Theme *create(QQmlEngine *, QJSEngine *); + bool dark() const; QColor fontPrimaryColor() const; void setFontPrimaryColor(const QColor &color); @@ -47,6 +67,7 @@ public: void setBlurBehindWindowEnabled(bool enabled); signals: + void darkChanged(); void fontPrimaryColorChanged(); void itemNormalColorChanged(); void windowBackgroundColorChanged(); @@ -55,7 +76,17 @@ signals: void blurBehindWindowEnabledChanged(); void itemHoverColorChanged(); +protected: + Theme(QObject *parent = nullptr); + void refreshColors(); + void checkUpdateDesktopImage(); + void timerEvent(QTimerEvent *event) final; + bool eventFilter(QObject *obj, QEvent *event) final; + private: + bool m_systemDark = false; + QMutex m_mutex; + QFileSystemWatcher m_watcher; QColor m_fontPrimaryColor; QColor m_itemNormalColor; QColor m_itemHoverColor; diff --git a/Fluent/Utilities.cpp b/Fluent/Utilities.cpp index 2091f5c..dd43438 100644 --- a/Fluent/Utilities.cpp +++ b/Fluent/Utilities.cpp @@ -1,5 +1,6 @@ #include "Utilities.h" #include +#include Utilities *Utilities::instance() { static Utilities *self = nullptr; @@ -18,10 +19,70 @@ Utilities *Utilities::create(QQmlEngine *, QJSEngine *) { 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) { 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) { return QUrl::fromLocalFile(path); } diff --git a/Fluent/Utilities.h b/Fluent/Utilities.h index 8279333..f6242dd 100644 --- a/Fluent/Utilities.h +++ b/Fluent/Utilities.h @@ -5,6 +5,84 @@ #include #include +#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 { Q_OBJECT QML_ELEMENT @@ -18,6 +96,10 @@ public: Q_INVOKABLE bool isMacos(); Q_INVOKABLE QRect desktopAvailableGeometry(QQuickWindow *window); 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: Utilities(QObject *parent = nullptr); diff --git a/Fluent/qml/Acrylic.qml b/Fluent/qml/Acrylic.qml index 61c97b0..0efb8c2 100644 --- a/Fluent/qml/Acrylic.qml +++ b/Fluent/qml/Acrylic.qml @@ -1,33 +1,33 @@ -import QtQuick - -Item { - id: control - property color tintColor: Qt.rgba(1, 1, 1, 1) - property real tintOpacity: 0.65 - property real luminosity: 0.01 - property real noiseOpacity: 0.02 - property var target - property int blurRadius: 32 - property rect targetRect: Qt.rect(control.x, control.y, control.width,control.height) - ShaderEffectSource { - id: effect_source - anchors.fill: parent - visible: false - sourceRect: control.targetRect - sourceItem: control.target - } - Rectangle { - anchors.fill: parent - color: Qt.rgba(1, 1, 1, luminosity) - } - Rectangle { - anchors.fill: parent - color: Qt.rgba(tintColor.r, tintColor.g, tintColor.b, tintOpacity) - } - Image { - anchors.fill: parent - source: "qrc:/qt/qml/Fluent/resources/noise.png" - fillMode: Image.Tile - opacity: control.noiseOpacity - } -} +import QtQuick + +Item { + id: control + property color tintColor: Qt.rgba(1, 1, 1, 1) + property real tintOpacity: 0.65 + property real luminosity: 0.01 + property real noiseOpacity: 0.02 + property var target + property int blurRadius: 32 + property rect targetRect: Qt.rect(control.x, control.y, control.width,control.height) + ShaderEffectSource { + id: effect_source + anchors.fill: parent + visible: false + sourceRect: control.targetRect + sourceItem: control.target + } + Rectangle { + anchors.fill: parent + color: Qt.rgba(1, 1, 1, luminosity) + } + Rectangle { + anchors.fill: parent + color: Qt.rgba(tintColor.r, tintColor.g, tintColor.b, tintOpacity) + } + Image { + anchors.fill: parent + source: "qrc:/qt/qml/Fluent/resources/noise.png" + fillMode: Image.Tile + opacity: control.noiseOpacity + } +} diff --git a/Fluent/qml/AppBar.qml b/Fluent/qml/AppBar.qml index 100cafb..996d43f 100644 --- a/Fluent/qml/AppBar.qml +++ b/Fluent/qml/AppBar.qml @@ -1,87 +1,281 @@ -import QtQuick as Quick -import QtQuick.Layouts -import Fluent - -Quick.Rectangle { - id: root - property bool showMinimize: true - property bool showMaximize: true - property bool showClose: true - property bool showStayTop: true - property bool showDark: false - property string title: "" - property url icon - property string maximizeText : qsTr("Maximize") - property Quick.color textColor: Theme.fontPrimaryColor - property Quick.color maximizeNormalColor: Theme.itemNormalColor - property Quick.color maximizeHoverColor: Theme.itemHoverColor - property bool isMac: Utilities.isMacos() - property alias buttonMaximize: btn_maximize - property alias layoutMacosButtons: layout_macos_buttons - property alias layoutStandardbuttons: layout_standard_buttons - - Quick.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 - } - } - - RowLayout { - id:layout_standard_buttons - height: parent.height - anchors.right: parent.right - spacing: 0 - - 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: root.textColor - text:d.isRestore?restoreText:maximizeText - iconSize: 11 - onClicked: maxClickListener() - } - } - - Quick.Loader{ - id:layout_macos_buttons - anchors{ - verticalCenter: parent.verticalCenter - left: parent.left - leftMargin: 10 - } - sourceComponent: isMac ? com_macos_buttons : undefined - Quick.Component.onDestruction: sourceComponent = undefined - } -} +import QtQuick as Quick +import QtQuick.Controls +import QtQuick.Window +import QtQuick.Layouts +import Fluent + +Quick.Rectangle{ + property string title: "" + property string darkText : qsTr("Dark") + property string lightText : qsTr("Light") + property string minimizeText : qsTr("Minimize") + property string restoreText : qsTr("Restore") + property string maximizeText : qsTr("Maximize") + property string closeText : qsTr("Close") + property string stayTopText : qsTr("Sticky on Top") + property string stayTopCancelText : qsTr("Sticky on Top cancelled") + property color textColor: Theme.fontPrimaryColor + property color minimizeNormalColor: Theme.itemNormalColor + property color minimizeHoverColor: Theme.itemHoverColor + property color minimizePressColor: Theme.itemPressColor + property color maximizeNormalColor: Theme.itemNormalColor + property color maximizeHoverColor: Theme.itemHoverColor + property color maximizePressColor: Theme.itemPressColor + property color closeNormalColor: Qt.rgba(0,0,0,0) + property color closeHoverColor: Qt.rgba(251/255,115/255,115/255,1) + property color closePressColor: Qt.rgba(251/255,115/255,115/255,0.8) + property bool showDark: false + property bool showClose: true + property bool showMinimize: true + property bool showMaximize: true + property bool showStayTop: true + property bool titleVisible: true + property url icon + property int iconSize: 20 + property bool isMac: Utilities.isMacos() + property color borerlessColor : Theme.primaryColor + property alias buttonStayTop: btn_stay_top + property alias buttonMinimize: btn_minimize + property alias buttonMaximize: btn_maximize + property alias buttonClose: btn_close + property alias buttonDark: btn_dark + property alias layoutMacosButtons: layout_macos_buttons + property alias layoutStandardbuttons: layout_standard_buttons + property var maxClickListener : function(){ + if(Utilities.isMacos()){ + if (d.win.visibility === Window.FullScreen || d.win.visibility === Window.Maximized) + d.win.showNormal() + else + d.win.showFullScreen() + }else{ + if (d.win.visibility === Window.Maximized || d.win.visibility === Window.FullScreen) + d.win.showNormal() + else + d.win.showMaximized() + d.hoverMaxBtn = false + } + } + property var minClickListener: function(){ + if(d.win.transientParent != null){ + d.win.transientParent.showMinimized() + }else{ + d.win.showMinimized() + } + } + property var closeClickListener : function(){ + d.win.close() + } + property var stayTopClickListener: function(){ + if(d.win instanceof Window){ + d.win.stayTop = !d.win.stayTop + } + } + property var darkClickListener: function(){ + if(Theme.dark){ + Theme.darkMode = ThemeType.Light + }else{ + Theme.darkMode = ThemeType.Dark + } + } + id:control + color: Qt.rgba(0,0,0,0) + height: visible ? 30 : 0 + opacity: visible + z: 65535 + 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 + } +} diff --git a/Fluent/qml/Button.qml b/Fluent/qml/Button.qml new file mode 100644 index 0000000..9a450f0 --- /dev/null +++ b/Fluent/qml/Button.qml @@ -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 + } +} diff --git a/Fluent/qml/ContentDialog.qml b/Fluent/qml/ContentDialog.qml new file mode 100644 index 0000000..9d21b1b --- /dev/null +++ b/Fluent/qml/ContentDialog.qml @@ -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() + } + } + } + } + } + } + } + } +} diff --git a/Fluent/qml/ControlBackground.qml b/Fluent/qml/ControlBackground.qml new file mode 100644 index 0000000..de3ad7d --- /dev/null +++ b/Fluent/qml/ControlBackground.qml @@ -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 + } +} diff --git a/Fluent/qml/FilledButton.qml b/Fluent/qml/FilledButton.qml new file mode 100644 index 0000000..7ddef52 --- /dev/null +++ b/Fluent/qml/FilledButton.qml @@ -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 + } +} diff --git a/Fluent/qml/FocusRectangle.qml b/Fluent/qml/FocusRectangle.qml new file mode 100644 index 0000000..9236646 --- /dev/null +++ b/Fluent/qml/FocusRectangle.qml @@ -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 + } +} diff --git a/Fluent/qml/Icon.qml b/Fluent/qml/Icon.qml index 5bbba9b..a813e03 100644 --- a/Fluent/qml/Icon.qml +++ b/Fluent/qml/Icon.qml @@ -1,19 +1,21 @@ -import QtQuick - -Text { - property int iconSource - property int iconSize: 20 - property color iconColor: FluTheme.dark ? "#FFFFFF" : "#000000" - id:control - font.family: font_loader.name - font.pixelSize: iconSize - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - color: iconColor - text: (String.fromCharCode(iconSource).toString(16)) - opacity: iconSource>0 - FontLoader{ - id: font_loader - source: "qrc:/qt/qml/FluentUI/Font/FluentIcons.ttf" - } -} +import QtQuick +import QtQuick.Controls +import Fluent + +Text { + property int iconSource + property int iconSize: 20 + property color iconColor: Theme.dark ? "#FFFFFF" : "#000000" + id:control + font.family: font_loader.name + font.pixelSize: iconSize + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: iconColor + text: (String.fromCharCode(iconSource).toString(16)) + opacity: iconSource>0 + FontLoader{ + id: font_loader + source: "qrc:/qt/qml/Fluent/resources/FluentIcons.ttf" + } +} diff --git a/Fluent/qml/IconButton.qml b/Fluent/qml/IconButton.qml index c28e4e7..f410514 100644 --- a/Fluent/qml/IconButton.qml +++ b/Fluent/qml/IconButton.qml @@ -1,30 +1,129 @@ -import QtQuick -import QtQuick.Controls - -Button { - property int iconSize: 20 - property int iconSource - property int radius:4 - property color color: { - if(!enabled){ - return disableColor - } - if(pressed){ - return pressedColor - } - return hovered ? hoverColor : normalColor - } - property color iconColor: { - if (FluTheme.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) - } - } -} +import QtQuick as Quick +import QtQuick.Controls as Controls +import QtQuick.Layouts +import Fluent + +Controls.Button { + display: Controls.Button.IconOnly + property int iconSize: 20 + property int iconSource + property bool disabled: false + property int radius:4 + property string contentDescription: "" + property Quick.color hoverColor: Theme.itemHoverColor + property Quick.color pressedColor: Theme.itemPressColor + property Quick.color normalColor: Theme.itemNormalColor + property Quick.color disableColor: Theme.itemNormalColor + property Quick.Component iconDelegate: com_icon + property Quick.color color: { + if(!enabled){ + return disableColor + } + if(pressed){ + return pressedColor + } + return hovered ? hoverColor : normalColor + } + 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 + } +} diff --git a/Fluent/qml/Image.qml b/Fluent/qml/Image.qml new file mode 100644 index 0000000..dcdc3fc --- /dev/null +++ b/Fluent/qml/Image.qml @@ -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() + } + } + } +} diff --git a/Fluent/qml/ImageButton.qml b/Fluent/qml/ImageButton.qml new file mode 100644 index 0000000..d264eff --- /dev/null +++ b/Fluent/qml/ImageButton.qml @@ -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 + } + } +} diff --git a/Fluent/qml/InfoBar.qml b/Fluent/qml/InfoBar.qml index 1fbbcca..84aab7e 100644 --- a/Fluent/qml/InfoBar.qml +++ b/Fluent/qml/InfoBar.qml @@ -1,245 +1,243 @@ -import QtQuick as Quick -import QtQuick.Controls -import Fluent - -Object { - property var root - property int layoutY: 75 - id:control - Object{ - id:mcontrol - property string const_success: "success" - property string const_info: "info" - property string const_warning: "warning" - property string const_error: "error" - property int maxWidth: 300 - property var screenLayout: null - function create(type,text,duration,moremsg){ - if(screenLayout){ - var last = screenLayout.getLastloader() - if(last.type === type && last.text === text && moremsg === last.moremsg){ - last.duration = duration - if (duration > 0) last.restart() - return last - } - } - initScreenLayout() - return contentComponent.createObject(screenLayout,{type:type,text:text,duration:duration,moremsg:moremsg,}) - } - function createCustom(itemcomponent,duration){ - initScreenLayout() - if(itemcomponent){ - return contentComponent.createObject(screenLayout,{itemcomponent:itemcomponent,duration:duration}) - } - } - function initScreenLayout(){ - if(screenLayout == null){ - screenLayout = screenlayoutComponent.createObject(root) - screenLayout.y = control.layoutY - screenLayout.z = 100000 - } - } - Quick.Component { - id:screenlayoutComponent - Quick.Column{ - parent: Overlay.overlay - z:999 - spacing: 20 - width: root.width - move: Quick.Transition { - Quick.NumberAnimation { - properties: "y" - easing.type: Easing.OutCubic - duration: FluTheme.animationEnabled ? 333 : 0 - } - } - onChildrenChanged: if(children.length === 0) destroy() - function getLastloader(){ - if(children.length > 0){ - return children[children.length - 1] - } - return null - } - } - } - Quick.Component{ - id:contentComponent - Quick.Item{ - id:content - property int duration: 1500 - property var itemcomponent - property string type - property string text - property string moremsg - width: parent.width - height: loader.height - function close(){ - content.destroy() - } - function restart(){ - delayTimer.restart() - } - Quick.Timer { - id:delayTimer - interval: duration - running: duration > 0 - repeat: duration > 0 - onTriggered: content.close() - } - Quick.Loader{ - id:loader - x:(parent.width - width) / 2 - property var _super: content - scale: item ? 1 : 0 - asynchronous: true - Quick.Behavior on scale { - enabled: FluTheme.animationEnabled - Quick.NumberAnimation { - easing.type: Easing.OutCubic - duration: 167 - } - } - 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) - height: rowlayout.height + 20 - color: { - if(FluTheme.dark){ - switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(57/255,61/255,27/255,1) - case mcontrol.const_warning: return Qt.rgba(67/255,53/255,25/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{ - switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(223/255,246/255,221/255,1) - case mcontrol.const_warning: return Qt.rgba(255/255,244/255,206/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) - } - } - Shadow { - radius: 4 - } - radius: 4 - border.width: 1 - border.color: { - if(FluTheme.dark){ - switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(56/255,61/255,27/255,1) - case mcontrol.const_warning: return Qt.rgba(66/255,53/255,25/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{ - switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(210/255,232/255,208/255,1) - case mcontrol.const_warning: return Qt.rgba(240/255,230/255,194/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) - } - } - Quick.Row{ - id:rowlayout - x:20 - y:(parent.height - height) / 2 - spacing: 10 - Icon{ - iconSource:{ - switch(_super.type){ - case mcontrol.const_success: return FluentIcons.CompletedSolid - case mcontrol.const_warning: return FluentIcons.InfoSolid - case mcontrol.const_info: return FluentIcons.InfoSolid - case mcontrol.const_error: return FluentIcons.StatusErrorFull - }FluentIcons.StatusErrorFull - return FluentIcons.FA_info_circle - } - iconSize:20 - iconColor: { - if(FluTheme.dark){ - switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1) - case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/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{ - switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(15/255,123/255,15/255,1) - case mcontrol.const_warning: return Qt.rgba(157/255,93/255,0/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) - } - } - } - - Quick.Column{ - spacing: 5 - Text{ - text:_super.text - wrapMode: Text.WrapAnywhere - width: Math.min(implicitWidth,mcontrol.maxWidth) - } - Text{ - text: _super.moremsg - visible: _super.moremsg - wrapMode : Text.WrapAnywhere - textColor: FluColors.Grey120 - width: Math.min(implicitWidth,mcontrol.maxWidth) - } - } - - IconButton{ - id:btn_close - iconSource: FluentIcons.ChromeClose - iconSize: 10 - verticalPadding: 0 - horizontalPadding: 0 - width: 30 - height: 20 - visible: _super.duration<=0 - anchors.verticalCenter: parent.verticalCenter - 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 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 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 clearAllInfo(){ - if(mcontrol.screenLayout != null) { - mcontrol.screenLayout.destroy() - mcontrol.screenLayout = null - } - - return true - } -} +import QtQuick as Quick +import QtQuick.Controls + +Object { + property var root + property int layoutY: 75 + id:control + Object{ + id:mcontrol + property string const_success: "success" + property string const_info: "info" + property string const_warning: "warning" + property string const_error: "error" + property int maxWidth: 300 + property var screenLayout: null + function create(type,text,duration,moremsg){ + if(screenLayout){ + var last = screenLayout.getLastloader() + if(last.type === type && last.text === text && moremsg === last.moremsg){ + last.duration = duration + if (duration > 0) last.restart() + return last + } + } + initScreenLayout() + return contentComponent.createObject(screenLayout,{type:type,text:text,duration:duration,moremsg:moremsg,}) + } + function createCustom(itemcomponent,duration){ + initScreenLayout() + if(itemcomponent){ + return contentComponent.createObject(screenLayout,{itemcomponent:itemcomponent,duration:duration}) + } + } + function initScreenLayout(){ + if(screenLayout == null){ + screenLayout = screenlayoutComponent.createObject(root) + screenLayout.y = control.layoutY + screenLayout.z = 100000 + } + } + Quick.Component{ + id:screenlayoutComponent + Quick.Column{ + parent: Overlay.overlay + z:999 + spacing: 20 + width: root.width + move: Quick.Transition { + Quick.NumberAnimation { + properties: "y" + easing.type: Easing.OutCubic + duration: FluTheme.animationEnabled ? 333 : 0 + } + } + onChildrenChanged: if(children.length === 0) destroy() + function getLastloader(){ + if(children.length > 0){ + return children[children.length - 1] + } + return null + } + } + } + Quick.Component{ + id:contentComponent + Quick.Item{ + id:content + property int duration: 1500 + property var itemcomponent + property string type + property string text + property string moremsg + width: parent.width + height: loader.height + function close(){ + content.destroy() + } + function restart(){ + delayTimer.restart() + } + Quick.Timer { + id:delayTimer + interval: duration + running: duration > 0 + repeat: duration > 0 + onTriggered: content.close() + } + Loader{ + id:loader + x:(parent.width - width) / 2 + property var _super: content + scale: item ? 1 : 0 + asynchronous: true + Quick.Behavior on scale { + enabled: FluTheme.animationEnabled + Quick.NumberAnimation { + easing.type: Easing.OutCubic + duration: 167 + } + } + sourceComponent:itemcomponent ? itemcomponent : mcontrol.fluent_sytle + } + } + } + property Quick.Component fluent_sytle: Quick.Rectangle{ + width: rowlayout.width + (btn_close.visible ? 30 : 48) + height: rowlayout.height + 20 + color: { + if(FluTheme.dark){ + switch(_super.type){ + case mcontrol.const_success: return Qt.rgba(57/255,61/255,27/255,1) + case mcontrol.const_warning: return Qt.rgba(67/255,53/255,25/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{ + switch(_super.type){ + case mcontrol.const_success: return Qt.rgba(223/255,246/255,221/255,1) + case mcontrol.const_warning: return Qt.rgba(255/255,244/255,206/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) + } + } + Shadow{ + radius: 4 + } + radius: 4 + border.width: 1 + border.color: { + if(FluTheme.dark){ + switch(_super.type){ + case mcontrol.const_success: return Qt.rgba(56/255,61/255,27/255,1) + case mcontrol.const_warning: return Qt.rgba(66/255,53/255,25/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{ + switch(_super.type){ + case mcontrol.const_success: return Qt.rgba(210/255,232/255,208/255,1) + case mcontrol.const_warning: return Qt.rgba(240/255,230/255,194/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) + } + } + Quick.Row{ + id:rowlayout + x:20 + y:(parent.height - height) / 2 + spacing: 10 + Icon{ + iconSource:{ + switch(_super.type){ + case mcontrol.const_success: return FluentIcons.CompletedSolid + case mcontrol.const_warning: return FluentIcons.InfoSolid + case mcontrol.const_info: return FluentIcons.InfoSolid + case mcontrol.const_error: return FluentIcons.StatusErrorFull + }FluentIcons.StatusErrorFull + return FluentIcons.FA_info_circle + } + iconSize:20 + iconColor: { + if(FluTheme.dark){ + switch(_super.type){ + case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1) + case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/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{ + switch(_super.type){ + case mcontrol.const_success: return Qt.rgba(15/255,123/255,15/255,1) + case mcontrol.const_warning: return Qt.rgba(157/255,93/255,0/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) + } + } + } + + Quick.Column{ + spacing: 5 + Text{ + text:_super.text + wrapMode: Text.WrapAnywhere + width: Math.min(implicitWidth,mcontrol.maxWidth) + } + Text{ + text: _super.moremsg + visible: _super.moremsg + wrapMode : Text.WrapAnywhere + textColor: FluColors.Grey120 + width: Math.min(implicitWidth,mcontrol.maxWidth) + } + } + + IconButton{ + id:btn_close + iconSource: FluentIcons.ChromeClose + iconSize: 10 + verticalPadding: 0 + horizontalPadding: 0 + width: 30 + height: 20 + visible: _super.duration<=0 + anchors.verticalCenter: parent.verticalCenter + 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 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 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 clearAllInfo(){ + if(mcontrol.screenLayout != null) { + mcontrol.screenLayout.destroy() + mcontrol.screenLayout = null + } + + return true + } +} diff --git a/Fluent/qml/Loader.qml b/Fluent/qml/Loader.qml new file mode 100644 index 0000000..fcf5dae --- /dev/null +++ b/Fluent/qml/Loader.qml @@ -0,0 +1,5 @@ +import QtQuick + +Loader { + Component.onDestruction: sourceComponent = undefined +} diff --git a/Fluent/qml/Object.qml b/Fluent/qml/Object.qml index f435c08..9d60081 100644 --- a/Fluent/qml/Object.qml +++ b/Fluent/qml/Object.qml @@ -1,7 +1,7 @@ -import QtQuick - -QtObject { - id:root - default property list children - -} +import QtQuick +import QtQuick.Controls + +QtObject { + default property list children + id:control +} diff --git a/Fluent/qml/Popup.qml b/Fluent/qml/Popup.qml new file mode 100644 index 0000000..dfc7174 --- /dev/null +++ b/Fluent/qml/Popup.qml @@ -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 + } + } +} diff --git a/Fluent/qml/ProgressRing.qml b/Fluent/qml/ProgressRing.qml new file mode 100644 index 0000000..694e6b6 --- /dev/null +++ b/Fluent/qml/ProgressRing.qml @@ -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 + } +} diff --git a/Fluent/qml/Router.qml b/Fluent/qml/Router.qml index 1aa4c63..3ab018d 100644 --- a/Fluent/qml/Router.qml +++ b/Fluent/qml/Router.qml @@ -1,12 +1,71 @@ -pragma Singleton - -import QtQml - -QtObject { - property var windows: [] - function addWindow(window){ - if(!window.transientParent){ - windows.push(window) - } - } -} +pragma Singleton +import QtQuick + + +QtObject { + property var routes : ({}) + property var windows: [] + function addWindow(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 + } + } +} diff --git a/Fluent/qml/ScrollBar.qml b/Fluent/qml/ScrollBar.qml new file mode 100644 index 0000000..e547aca --- /dev/null +++ b/Fluent/qml/ScrollBar.qml @@ -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 + } + } + } + ] + } +} diff --git a/Fluent/qml/Shadow.qml b/Fluent/qml/Shadow.qml index 7b68cf1..3f88e10 100644 --- a/Fluent/qml/Shadow.qml +++ b/Fluent/qml/Shadow.qml @@ -1,14 +1,15 @@ -import QtQuick +import QtQuick as Quick +import Fluent -Item { - property color color: FluTheme.dark ? "#000000" : "#999999" +Quick.Item { + property Quick.color color: Theme.dark ? "#000000" : "#999999" property int elevation: 5 property int radius: 4 id:control anchors.fill: parent - Repeater{ + Quick.Repeater{ model: elevation - Rectangle{ + Quick.Rectangle{ anchors.fill: parent color: "#00000000" opacity: 0.01 * (elevation-index+1) diff --git a/Fluent/qml/Text.qml b/Fluent/qml/Text.qml index 899646a..b74f8ec 100644 --- a/Fluent/qml/Text.qml +++ b/Fluent/qml/Text.qml @@ -1,11 +1,10 @@ import QtQuick as Quick - import Fluent Quick.Text { - property Quick.color textColor: FluTheme.fontPrimaryColor + property Quick.color textColor: Theme.fontPrimaryColor id:text color: textColor - renderType: FluTheme.nativeText ? Text.NativeRendering : Text.QtRendering - font: FluTextStyle.Body + renderType: Theme.nativeText ? Text.NativeRendering : Text.QtRendering + font: TextStyle.Body } diff --git a/Fluent/qml/Tooltip.qml b/Fluent/qml/Tooltip.qml new file mode 100644 index 0000000..5fbfbd8 --- /dev/null +++ b/Fluent/qml/Tooltip.qml @@ -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 + } + } +} diff --git a/Fluent/qml/Window.qml b/Fluent/qml/Window.qml index 1631d15..5f00d64 100644 --- a/Fluent/qml/Window.qml +++ b/Fluent/qml/Window.qml @@ -1,186 +1,387 @@ -import QtQuick as Quick -import Fluent - -Quick.Window { - id: root - property string windowIcon: App.windowIcon - property bool showStayTop: false - property bool showMaximize: true - property bool showMinimize: true - property bool showClose: true - property bool showDark: false - property bool fixSize: false - property bool stayTop: false - property int __margins: 0 - property var background : com_background - property Quick.color backgroundColor: { - if(active){ - return Theme.windowActiveBackgroundColor - } - return Theme.windowBackgroundColor - } - property Quick.Item appBar: AppBar { - title: root.title - height: 30 - showDark: root.showDark - showClose: root.showClose - showMinimize: root.showMinimize - showMaximize: root.showMaximize - showStayTop: root.showStayTop - icon: root.windowIcon - } - Frameless { - id: frameless - appBar: root.appBar - maximizeButton: appBar.buttonMaximize - fixSize: root.fixSize - topmost: root.stayTop - disabled: App.useSystemAppBar - Quick.Component.onCompleted: { - frameless.setHitTestVisible(appBar.layoutMacosButtons) - frameless.setHitTestVisible(appBar.layoutStandardbuttons) - } - Quick.Component.onDestruction: { - frameless.onDestruction() - } - } - Quick.Component.onCompleted: { - Router.addWindow(root) - } - - Quick.Component { - id:com_app_bar - Quick.Item{ - data: root.appBar - Quick.Component.onCompleted: { - root.appBar.width = Qt.binding(function(){ - return this.parent.width - }) - } - } - } - - - Quick.Item{ - id: layout_container - anchors.fill: parent - anchors.margins: root.__margins - Quick.Loader{ - anchors.fill: parent - sourceComponent: background - Quick.Component.onDestruction: sourceComponent = undefined - } - Quick.Loader{ - id:loader_app_bar - anchors { - top: parent.top - left: parent.left - right: parent.right - } - height: { - if(root.useSystemAppBar){ - return 0 - } - return root.fitsAppBarWindows ? 0 : root.appBar.height - } - sourceComponent: root.useSystemAppBar ? undefined : com_app_bar - Quick.Component.onDestruction: sourceComponent = undefined - } - Quick.Item{ - id:layout_content - anchors{ - top: loader_app_bar.bottom - left: parent.left - right: parent.right - bottom: parent.bottom - } - clip: true - } - Quick.Loader{ - property string loadingText - property bool cancel: false - id:loader_loading - anchors.fill: parent - Quick.Component.onDestruction: sourceComponent = undefined - } - InfoBar{ - id:info_bar - root: layout_container - } - - Quick.Loader{ - id:loader_border - anchors.fill: parent - sourceComponent: { - if(root.useSystemAppBar || Utilities.isWin() || root.visibility === Window.Maximized || root.visibility === Window.FullScreen){ - return undefined - } - return com_border - } - Quick.Component.onDestruction: sourceComponent = undefined - } - } - - Quick.Component { - id:com_background - Quick.Item{ - Rectangle{ - anchors.fill: parent - color: root.backgroundColor - } - Quick.Image{ - id:img_back - visible: false - cache: false - fillMode: Quick.Image.PreserveAspectCrop - asynchronous: true - Quick.Component.onCompleted: { - img_back.updateLayout() - source = Utilities.getUrlByFilePath(Theme.desktopImagePath) - } - Quick.Connections{ - target: root - function onScreenChanged(){ - img_back.updateLayout() - } - } - function updateLayout(){ - var geometry = Utilities.desktopAvailableGeometry(root) - img_back.width = geometry.width - img_back.height = geometry.height - img_back.sourceSize = Qt.size(img_back.width,img_back.height) - } - Quick.Connections{ - target: Theme - function onDesktopImagePathChanged(){ - timer_update_image.restart() - } - function onBlurBehindWindowEnabledChanged(){ - if(Theme.blurBehindWindowEnabled){ - img_back.source = Utilities.getUrlByFilePath(Theme.desktopImagePath) - }else{ - img_back.source = "" - } - } - } - Quick.Timer{ - id:timer_update_image - interval: 150 - onTriggered: { - img_back.source = "" - img_back.source = Utilities.getUrlByFilePath(Theme.desktopImagePath) - } - } - } - Acrylic{ - anchors.fill: parent - target: img_back - tintOpacity: Theme.dark ? 0.80 : 0.75 - blurRadius: 64 - visible: root.active && Theme.blurBehindWindowEnabled - tintColor: Theme.dark ? Qt.rgba(0, 0, 0, 1) : Qt.rgba(1, 1, 1, 1) - targetRect: Qt.rect(root.x-root.screen.virtualX,root.y-root.screen.virtualY,root.width,root.height) - } - } - } -} +import QtQuick as Quick +import QtQuick.Controls +import QtQuick.Layouts +import Fluent + +Quick.Window { + id: window + default property alias contentData : layout_content.data + property string windowIcon: App.windowIcon + property int launchMode: WindowType.Standard + property var argument:({}) + property var background : com_background + property bool fixSize: false + property Quick.Component loadingItem: com_loading + property bool fitsAppBarWindows: false + property var tintOpacity: Theme.dark ? 0.80 : 0.75 + property int blurRadius: 60 + property alias effect: frameless.effect + readonly property alias effective: frameless.effective + readonly property var availableEffects: frameless.availableEffects + property Quick.Item appBar: AppBar { + title: window.title + height: 30 + showDark: window.showDark + showClose: window.showClose + showMinimize: window.showMinimize + showMaximize: window.showMaximize + showStayTop: window.showStayTop + icon: window.windowIcon + } + property Quick.color backgroundColor: { + if(frameless.effective && active){ + var backcolor + if(frameless.effect==="dwm-blur"){ + backcolor = Utilities.withOpacity(Theme.windowActiveBackgroundColor, window.tintOpacity) + }else{ + backcolor = "transparent" + } + return backcolor + } + if(active){ + return Theme.windowActiveBackgroundColor + } + return Theme.windowBackgroundColor + } + property bool stayTop: false + property bool showDark: false + property bool showClose: true + property bool showMinimize: true + property bool showMaximize: true + property bool showStayTop: false + property bool autoMaximize: false + property bool autoVisible: true + property bool autoCenter: true + property bool autoDestroy: true + property bool useSystemAppBar + 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) + } + property int resizeBorderWidth: 1 + property var closeListener: function(event){ + if(autoDestroy){ + Router.removeWindow(window) + }else{ + window.visibility = Window.Hidden + event.accepted = false + } + } + signal initArgument(var argument) + signal lazyLoad() + property var _windowRegister + property string _route + property bool _hideShadow: false + color: Utilities.isSoftware() ? window.backgroundColor : "transparent" + Quick.Component.onCompleted: { + Router.addWindow(window) + useSystemAppBar = App.useSystemAppBar + if(!useSystemAppBar && autoCenter){ + moveWindowToDesktopCenter() + } + fixWindowSize() + initArgument(argument) + if(window.autoVisible){ + if(window.autoMaximize){ + window.visibility = Window.Maximized + }else{ + window.show() + } + } + } + onVisibleChanged: { + if(visible && d.isLazyInit){ + window.lazyLoad() + d.isLazyInit = false + } + } + Quick.QtObject{ + id:d + property bool isLazyInit: true + } + Quick.Connections{ + target: window + function onClosing(event){closeListener(event)} + } + Frameless{ + id: frameless + appBar: window.appBar + maximizeButton: appBar.buttonMaximize + fixSize: window.fixSize + topmost: window.stayTop + disabled: App.useSystemAppBar + Quick.Component.onCompleted: { + frameless.setHitTestVisible(appBar.layoutMacosButtons) + frameless.setHitTestVisible(appBar.layoutStandardbuttons) + } + Quick.Component.onDestruction: { + frameless.onDestruction() + } + onEffectiveChanged: { + if(effective){ + Theme.blurBehindWindowEnabled = false + } + } + } + Quick.Component{ + id:com_background + Quick.Item{ + Quick.Rectangle{ + anchors.fill: parent + color: window.backgroundColor + } + Quick.Image{ + id:img_back + visible: false + cache: false + fillMode: Quick.Image.PreserveAspectCrop + asynchronous: true + Quick.Component.onCompleted: { + img_back.updateLayout() + source = Utilities.getUrlByFilePath(Theme.desktopImagePath) + } + Quick.Connections{ + target: window + function onScreenChanged(){ + img_back.updateLayout() + } + } + function updateLayout(){ + var geometry = Utilities.desktopAvailableGeometry(window) + img_back.width = geometry.width + img_back.height = geometry.height + img_back.sourceSize = Qt.size(img_back.width,img_back.height) + } + Quick.Connections{ + target: Theme + function onDesktopImagePathChanged(){ + timer_update_image.restart() + } + function onBlurBehindWindowEnabledChanged(){ + if(Theme.blurBehindWindowEnabled){ + img_back.source = Utilities.getUrlByFilePath(Theme.desktopImagePath) + }else{ + img_back.source = "" + } + } + } + Quick.Timer{ + id:timer_update_image + interval: 150 + onTriggered: { + img_back.source = "" + img_back.source = Utilities.getUrlByFilePath(Theme.desktopImagePath) + } + } + } + Acrylic{ + anchors.fill: parent + 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 + } +} diff --git a/Fluent/resources/FluentIcons.ttf b/Fluent/resources/FluentIcons.ttf new file mode 100644 index 0000000..8f05a4b Binary files /dev/null and b/Fluent/resources/FluentIcons.ttf differ diff --git a/Fluent/resources/btn_close_hovered.png b/Fluent/resources/btn_close_hovered.png new file mode 100644 index 0000000..afd646f Binary files /dev/null and b/Fluent/resources/btn_close_hovered.png differ diff --git a/Fluent/resources/btn_close_normal.png b/Fluent/resources/btn_close_normal.png new file mode 100644 index 0000000..de922d4 Binary files /dev/null and b/Fluent/resources/btn_close_normal.png differ diff --git a/Fluent/resources/btn_close_pushed.png b/Fluent/resources/btn_close_pushed.png new file mode 100644 index 0000000..4c722ba Binary files /dev/null and b/Fluent/resources/btn_close_pushed.png differ diff --git a/Fluent/resources/btn_max_hovered.png b/Fluent/resources/btn_max_hovered.png new file mode 100644 index 0000000..22ba4ca Binary files /dev/null and b/Fluent/resources/btn_max_hovered.png differ diff --git a/Fluent/resources/btn_max_normal.png b/Fluent/resources/btn_max_normal.png new file mode 100644 index 0000000..4c1bc62 Binary files /dev/null and b/Fluent/resources/btn_max_normal.png differ diff --git a/Fluent/resources/btn_max_pushed.png b/Fluent/resources/btn_max_pushed.png new file mode 100644 index 0000000..22fc333 Binary files /dev/null and b/Fluent/resources/btn_max_pushed.png differ diff --git a/Fluent/resources/btn_min_hovered.png b/Fluent/resources/btn_min_hovered.png new file mode 100644 index 0000000..41e9395 Binary files /dev/null and b/Fluent/resources/btn_min_hovered.png differ diff --git a/Fluent/resources/btn_min_normal.png b/Fluent/resources/btn_min_normal.png new file mode 100644 index 0000000..25fcf5f Binary files /dev/null and b/Fluent/resources/btn_min_normal.png differ diff --git a/Fluent/resources/btn_min_pushed.png b/Fluent/resources/btn_min_pushed.png new file mode 100644 index 0000000..57f7211 Binary files /dev/null and b/Fluent/resources/btn_min_pushed.png differ