add basic code.

This commit is contained in:
amass 2024-08-23 00:04:16 +08:00
parent b23259fad4
commit 521c40cbd5
16 changed files with 1138 additions and 3 deletions

View File

@ -21,12 +21,20 @@ qt6_add_qml_module(Fluent
Icons.h Icons.h
Rectangle.h Rectangle.cpp Rectangle.h Rectangle.cpp
Theme.h Theme.cpp Theme.h Theme.cpp
Utilities.h Utilities.cpp
QML_FILES QML_FILES
qml/Acrylic.qml
qml/AppBar.qml qml/AppBar.qml
qml/Icon.qml
qml/IconButton.qml qml/IconButton.qml
qml/InfoBar.qml
qml/Object.qml
qml/Router.qml qml/Router.qml
qml/Shadow.qml
qml/Text.qml qml/Text.qml
qml/Window.qml qml/Window.qml
RESOURCES
resources/noise.png
) )
target_include_directories(Fluent target_include_directories(Fluent

View File

@ -1,6 +1,39 @@
#include "Frameless.h" #include "Frameless.h"
#include "Utilities.h"
#include <QQuickWindow>
#include <dwmapi.h>
#include <qt_windows.h>
#include <windowsx.h>
static inline void setShadow(HWND hwnd) {
const MARGINS shadow = {1, 0, 0, 0};
typedef HRESULT(WINAPI * DwmExtendFrameIntoClientAreaPtr)(HWND hWnd, const MARGINS *pMarInset);
HMODULE module = LoadLibrary(L"dwmapi.dll");
if (module) {
DwmExtendFrameIntoClientAreaPtr dwm_extendframe_into_client_area_;
dwm_extendframe_into_client_area_ =
reinterpret_cast<DwmExtendFrameIntoClientAreaPtr>(GetProcAddress(module, "DwmExtendFrameIntoClientArea"));
if (dwm_extendframe_into_client_area_) {
dwm_extendframe_into_client_area_(hwnd, &shadow);
}
}
}
static bool containsCursorToItem(QQuickItem *item) {
auto window = item->window();
if ((window == nullptr) || !item || !item->isVisible()) {
return false;
}
auto point = window->mapFromGlobal(QCursor::pos());
auto rect = QRectF(item->mapToItem(window->contentItem(), QPointF(0, 0)), item->size());
if (rect.contains(point)) {
return true;
}
return false;
}
Frameless::Frameless(QQuickItem *parent) : QQuickItem{parent} { Frameless::Frameless(QQuickItem *parent) : QQuickItem{parent} {
m_isWindows11OrGreater = Utilities::instance()->isWindows11OrGreater();
} }
QQuickItem *Frameless::appBar() const { QQuickItem *Frameless::appBar() const {
@ -25,6 +58,28 @@ void Frameless::setMaximizeButton(QQuickItem *button) {
} }
} }
QQuickItem *Frameless::minimizedButton() const {
return m_minimizedButton;
}
void Frameless::setMinimizedButton(QQuickItem *button) {
if (m_minimizedButton != button) {
m_minimizedButton = button;
emit minimizedButtonChanged();
}
}
QQuickItem *Frameless::closeButton() const {
return m_closeButton;
}
void Frameless::setCloseButton(QQuickItem *button) {
if (m_closeButton != button) {
m_closeButton = button;
emit closeButtonChanged();
}
}
bool Frameless::fixSize() const { bool Frameless::fixSize() const {
return m_fixSize; return m_fixSize;
} }
@ -69,8 +124,370 @@ void Frameless::onDestruction() {
} }
void Frameless::componentComplete() { void Frameless::componentComplete() {
if (m_disabled) return;
int w = window()->width();
int h = window()->height();
m_current = window()->winId();
window()->setFlags((window()->flags()) | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint |
Qt::WindowCloseButtonHint);
if (!m_fixSize) {
window()->setFlag(Qt::WindowMaximizeButtonHint);
}
window()->installEventFilter(this);
QGuiApplication::instance()->installNativeEventFilter(this);
if (m_maximizeButton) {
setHitTestVisible(m_maximizeButton);
}
if (m_minimizedButton) {
setHitTestVisible(m_minimizedButton);
}
if (m_closeButton) {
setHitTestVisible(m_closeButton);
}
#ifdef Q_OS_WIN
#if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3))
qWarning() << "Qt's own frameless bug, currently only exist in 6.5.3, please use other versions";
#endif
HWND hwnd = reinterpret_cast<HWND>(window()->winId());
DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE);
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,
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_FRAMECHANGED);
});
}
} else {
::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
}
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
connect(window(), &QQuickWindow::screenChanged, this, [hwnd] {
::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOOWNERZORDER);
::RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW);
});
if (!window()->property("_hideShadow").toBool()) {
setShadow(hwnd);
}
#endif
auto appBarHeight = m_appBar->height();
h = qRound(h + appBarHeight);
if (m_fixSize) {
window()->setMaximumSize(QSize(w, h));
window()->setMinimumSize(QSize(w, h));
} else {
window()->setMinimumHeight(window()->minimumHeight() + appBarHeight);
window()->setMaximumHeight(window()->maximumHeight() + appBarHeight);
}
window()->resize(QSize(w, h));
connect(this, &Frameless::topmostChanged, this, [this] { setWindowTopmost(topmost()); });
setWindowTopmost(topmost());
}
bool Frameless::eventFilter(QObject *obj, QEvent *event) {
#ifndef Q_OS_WIN
switch (ev->type()) {
case QEvent::MouseButtonPress:
if (_edges != 0) {
QMouseEvent *event = static_cast<QMouseEvent *>(ev);
if (event->button() == Qt::LeftButton) {
_updateCursor(_edges);
window()->startSystemResize(Qt::Edges(_edges));
}
} else {
if (_hitAppBar()) {
qint64 clickTimer = QDateTime::currentMSecsSinceEpoch();
qint64 offset = clickTimer - this->_clickTimer;
this->_clickTimer = clickTimer;
if (offset < 300) {
if (_isMaximized()) {
showNormal();
} else {
showMaximized();
}
} else {
window()->startSystemMove();
}
}
}
break;
case QEvent::MouseButtonRelease:
_edges = 0;
break;
case QEvent::MouseMove: {
if (_isMaximized() || _isFullScreen()) {
break;
}
if (_fixSize) {
break;
}
QMouseEvent *event = static_cast<QMouseEvent *>(ev);
QPoint p =
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
event->pos();
#else
event->position().toPoint();
#endif
if (p.x() >= _margins && p.x() <= (window()->width() - _margins) && p.y() >= _margins &&
p.y() <= (window()->height() - _margins)) {
if (_edges != 0) {
_edges = 0;
_updateCursor(_edges);
}
break;
}
_edges = 0;
if (p.x() < _margins) {
_edges |= Qt::LeftEdge;
}
if (p.x() > (window()->width() - _margins)) {
_edges |= Qt::RightEdge;
}
if (p.y() < _margins) {
_edges |= Qt::TopEdge;
}
if (p.y() > (window()->height() - _margins)) {
_edges |= Qt::BottomEdge;
}
_updateCursor(_edges);
break;
}
default:
break;
}
#endif
return QObject::eventFilter(obj, event);
} }
bool Frameless::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) { bool Frameless::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) {
#ifdef Q_OS_WIN
if ((eventType != "windows_generic_MSG") || !message) {
return false; return false;
} }
const auto msg = static_cast<const MSG *>(message);
auto hwnd = msg->hwnd;
if (!hwnd) {
return false;
}
const quint64 wid = reinterpret_cast<qint64>(hwnd);
if (wid != m_current) {
return false;
}
const auto uMsg = msg->message;
const auto wParam = msg->wParam;
const auto lParam = msg->lParam;
if (uMsg == WM_WINDOWPOSCHANGING) {
auto *wp = reinterpret_cast<WINDOWPOS *>(lParam);
if (wp != nullptr && (wp->flags & SWP_NOSIZE) == 0) {
wp->flags |= SWP_NOCOPYBITS;
*result = static_cast<qintptr>(::DefWindowProcW(hwnd, uMsg, wParam, lParam));
return true;
}
return false;
} else if (uMsg == WM_NCCALCSIZE && wParam == TRUE) {
bool isMaximum = ::IsZoomed(hwnd);
if (isMaximum) {
window()->setProperty("__margins", 7);
} else {
window()->setProperty("__margins", 0);
}
setMaximizeHovered(false);
*result = WVR_REDRAW;
return true;
} else if (uMsg == WM_NCHITTEST) {
if (m_isWindows11OrGreater) {
if (hitMaximizeButton()) {
if (*result == HTNOWHERE) {
*result = HTZOOM;
}
setMaximizeHovered(true);
return true;
}
setMaximizeHovered(false);
setMaximizePressed(false);
}
*result = 0;
POINT nativeGlobalPos{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
POINT nativeLocalPos = nativeGlobalPos;
::ScreenToClient(hwnd, &nativeLocalPos);
RECT clientRect{0, 0, 0, 0};
::GetClientRect(hwnd, &clientRect);
auto clientWidth = clientRect.right - clientRect.left;
auto clientHeight = clientRect.bottom - clientRect.top;
bool left = nativeLocalPos.x < m_margins;
bool right = nativeLocalPos.x > clientWidth - m_margins;
bool top = nativeLocalPos.y < m_margins;
bool bottom = nativeLocalPos.y > clientHeight - m_margins;
*result = 0;
if (!m_fixSize && !isFullScreen() && !isMaximized()) {
if (left && top) {
*result = HTTOPLEFT;
} else if (left && bottom) {
*result = HTBOTTOMLEFT;
} else if (right && top) {
*result = HTTOPRIGHT;
} else if (right && bottom) {
*result = HTBOTTOMRIGHT;
} else if (left) {
*result = HTLEFT;
} else if (right) {
*result = HTRIGHT;
} else if (top) {
*result = HTTOP;
} else if (bottom) {
*result = HTBOTTOM;
}
}
if (0 != *result) {
return true;
}
if (hitAppBar()) {
*result = HTCAPTION;
return true;
}
*result = HTCLIENT;
return true;
} else if (uMsg == WM_NCPAINT) {
*result = FALSE;
return false;
} else if (uMsg == WM_NCACTIVATE) {
*result = TRUE;
return true;
} else if (m_isWindows11OrGreater && (uMsg == WM_NCLBUTTONDBLCLK || uMsg == WM_NCLBUTTONDOWN)) {
if (hitMaximizeButton()) {
QMouseEvent event = QMouseEvent(QEvent::MouseButtonPress, QPoint(), QPoint(), Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier);
QGuiApplication::sendEvent(m_maximizeButton, &event);
setMaximizePressed(true);
return true;
}
} else if (m_isWindows11OrGreater && (uMsg == WM_NCLBUTTONUP || uMsg == WM_NCRBUTTONUP)) {
if (hitMaximizeButton()) {
QMouseEvent event = QMouseEvent(QEvent::MouseButtonRelease, QPoint(), QPoint(), Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier);
QGuiApplication::sendEvent(m_maximizeButton, &event);
setMaximizePressed(false);
return true;
}
} else if (uMsg == WM_NCRBUTTONDOWN) {
if (wParam == HTCAPTION) {
auto pos = window()->position();
auto offset = window()->mapFromGlobal(QCursor::pos());
showSystemMenu(QPoint(pos.x() + offset.x(), pos.y() + offset.y()));
}
} else if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) {
const bool altPressed = ((wParam == VK_MENU) || (::GetKeyState(VK_MENU) < 0));
const bool spacePressed = ((wParam == VK_SPACE) || (::GetKeyState(VK_SPACE) < 0));
if (altPressed && spacePressed) {
auto pos = window()->position();
showSystemMenu(QPoint(pos.x(), qRound(pos.y() + m_appBar->height())));
}
} else if (uMsg == WM_SYSCOMMAND) {
if (wParam == SC_MINIMIZE) {
if (window()->transientParent()) {
auto _hwnd = reinterpret_cast<HWND>(window()->transientParent()->winId());
::ShowWindow(_hwnd, 2);
} else {
auto _hwnd = reinterpret_cast<HWND>(window()->winId());
::ShowWindow(_hwnd, 2);
}
return true;
}
return false;
}
return false;
#else
return false;
#endif
}
bool Frameless::isFullScreen() {
return window()->visibility() == QWindow::FullScreen;
}
bool Frameless::isMaximized() {
return window()->visibility() == QWindow::Maximized;
}
void Frameless::setMaximizeHovered(bool val) {
if (m_maximizeButton) {
m_maximizeButton->setProperty("hover", val);
}
}
void Frameless::setMaximizePressed(bool val) {
if (m_maximizeButton) {
m_maximizeButton->setProperty("down", val);
}
}
void Frameless::setWindowTopmost(bool topmost) {
#ifdef Q_OS_WIN
HWND hwnd = reinterpret_cast<HWND>(window()->winId());
if (topmost) {
::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
} else {
::SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
#else
window()->setFlag(Qt::WindowStaysOnTopHint, topmost);
#endif
}
bool Frameless::hitMaximizeButton() {
if (containsCursorToItem(m_maximizeButton)) {
return true;
}
return false;
}
bool Frameless::hitAppBar() {
for (int i = 0; i <= m_hitTestList.size() - 1; ++i) {
auto item = m_hitTestList.at(i);
if (containsCursorToItem(item)) {
return false;
}
}
if ((m_appBar != nullptr) && containsCursorToItem(m_appBar)) {
return true;
}
return false;
}
void Frameless::showSystemMenu(QPoint point) {
#ifdef Q_OS_WIN
QScreen *screen = window()->screen();
if (!screen) {
screen = QGuiApplication::primaryScreen();
}
if (!screen) {
return;
}
const QPoint origin = screen->geometry().topLeft();
auto nativePos = QPointF(QPointF(point - origin) * window()->devicePixelRatio()).toPoint() + origin;
HWND hwnd = reinterpret_cast<HWND>(window()->winId());
auto hMenu = ::GetSystemMenu(hwnd, FALSE);
if (isMaximized() || isFullScreen()) {
::EnableMenuItem(hMenu, SC_MOVE, MFS_DISABLED);
::EnableMenuItem(hMenu, SC_RESTORE, MFS_ENABLED);
} else {
::EnableMenuItem(hMenu, SC_MOVE, MFS_ENABLED);
::EnableMenuItem(hMenu, SC_RESTORE, MFS_DISABLED);
}
if (!m_fixSize && !isMaximized() && !isFullScreen()) {
::EnableMenuItem(hMenu, SC_SIZE, MFS_ENABLED);
::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_ENABLED);
} else {
::EnableMenuItem(hMenu, SC_SIZE, MFS_DISABLED);
::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_DISABLED);
}
const int result =
::TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
nativePos.x(), nativePos.y(), 0, hwnd, nullptr);
if (result) {
::PostMessageW(hwnd, WM_SYSCOMMAND, result, 0);
}
#endif
}

View File

@ -9,6 +9,9 @@ class Frameless : public QQuickItem, QAbstractNativeEventFilter {
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QQuickItem *appBar READ appBar WRITE setAppBar NOTIFY appBarChanged) Q_PROPERTY(QQuickItem *appBar READ appBar WRITE setAppBar NOTIFY appBarChanged)
Q_PROPERTY(QQuickItem *maximizeButton READ maximizeButton WRITE setMaximizeButton NOTIFY maximizeButtonChanged) Q_PROPERTY(QQuickItem *maximizeButton READ maximizeButton WRITE setMaximizeButton NOTIFY maximizeButtonChanged)
Q_PROPERTY(QQuickItem *minimizedButton READ minimizedButton WRITE setMinimizedButton NOTIFY minimizedButtonChanged)
Q_PROPERTY(QQuickItem *closeButton READ closeButton WRITE setCloseButton NOTIFY closeButtonChanged)
Q_PROPERTY(bool fixSize READ fixSize WRITE setFixSize NOTIFY fixSizeChanged) Q_PROPERTY(bool fixSize READ fixSize WRITE setFixSize NOTIFY fixSizeChanged)
Q_PROPERTY(bool topmost READ topmost WRITE setTopmost NOTIFY topmostChanged) Q_PROPERTY(bool topmost READ topmost WRITE setTopmost NOTIFY topmostChanged)
Q_PROPERTY(bool disabled READ disabled WRITE setDisabled NOTIFY disabledChanged) Q_PROPERTY(bool disabled READ disabled WRITE setDisabled NOTIFY disabledChanged)
@ -21,6 +24,12 @@ public:
QQuickItem *maximizeButton() const; QQuickItem *maximizeButton() const;
void setMaximizeButton(QQuickItem *button); void setMaximizeButton(QQuickItem *button);
QQuickItem *minimizedButton() const;
void setMinimizedButton(QQuickItem *button);
QQuickItem *closeButton() const;
void setCloseButton(QQuickItem *button);
bool fixSize() const; bool fixSize() const;
void setFixSize(bool fix); void setFixSize(bool fix);
@ -39,17 +48,35 @@ public:
signals: signals:
void appBarChanged(); void appBarChanged();
void maximizeButtonChanged(); void maximizeButtonChanged();
void minimizedButtonChanged();
void closeButtonChanged();
void fixSizeChanged(); void fixSizeChanged();
void topmostChanged(); void topmostChanged();
void disabledChanged(); void disabledChanged();
protected:
bool isFullScreen();
bool isMaximized();
void setMaximizeHovered(bool val);
void setMaximizePressed(bool val);
void setWindowTopmost(bool topmost);
bool hitMaximizeButton();
bool hitAppBar();
void showSystemMenu(QPoint point);
bool eventFilter(QObject *obj, QEvent *event) final;
private: private:
quint64 m_current = 0;
QQuickItem *m_appBar = nullptr; QQuickItem *m_appBar = nullptr;
QQuickItem *m_maximizeButton = nullptr; QQuickItem *m_maximizeButton = nullptr;
QQuickItem *m_minimizedButton = nullptr;
QQuickItem *m_closeButton = nullptr;
bool m_fixSize = false; bool m_fixSize = false;
bool m_topmost = false; bool m_topmost = false;
bool m_disabled = false; bool m_disabled = false;
int m_margins = 8;
QList<QPointer<QQuickItem>> m_hitTestList; QList<QPointer<QQuickItem>> m_hitTestList;
bool m_isWindows11OrGreater = false;
}; };
#endif // FRAMELESS_H #endif // FRAMELESS_H

View File

@ -24,3 +24,58 @@ void Theme::setItemNormalColor(const QColor &color) {
emit itemNormalColorChanged(); emit itemNormalColorChanged();
} }
} }
QColor Theme::itemHoverColor() const {
return m_itemHoverColor;
}
void Theme::setItemHoverColor(const QColor &color) {
if (m_itemHoverColor != color) {
m_itemHoverColor = color;
emit itemHoverColorChanged();
}
}
QColor Theme::windowBackgroundColor() const {
return m_windowBackgroundColor;
}
void Theme::setWindowBackgroundColor(const QColor &color) {
if (m_windowBackgroundColor != color) {
m_windowBackgroundColor = color;
emit windowBackgroundColorChanged();
}
}
QColor Theme::windowActiveBackgroundColor() const {
return m_windowActiveBackgroundColor;
}
void Theme::setWindowActiveBackgroundColor(const QColor &color) {
if (m_windowActiveBackgroundColor != color) {
m_windowActiveBackgroundColor = color;
emit windowActiveBackgroundColorChanged();
}
}
QString Theme::desktopImagePath() const {
return m_desktopImagePath;
}
void Theme::setDesktopImagePath(const QString &path) {
if (m_desktopImagePath != path) {
m_desktopImagePath = path;
emit desktopImagePathChanged();
}
}
bool Theme::blurBehindWindowEnabled() const {
return m_blurBehindWindowEnabled;
}
void Theme::setBlurBehindWindowEnabled(bool enabled) {
if (m_blurBehindWindowEnabled != enabled) {
m_blurBehindWindowEnabled = enabled;
emit blurBehindWindowEnabledChanged();
}
}

View File

@ -11,6 +11,16 @@ class Theme : public QObject {
QML_SINGLETON QML_SINGLETON
Q_PROPERTY(QColor fontPrimaryColor READ fontPrimaryColor WRITE setFontPrimaryColor NOTIFY fontPrimaryColorChanged) Q_PROPERTY(QColor fontPrimaryColor READ fontPrimaryColor WRITE setFontPrimaryColor NOTIFY fontPrimaryColorChanged)
Q_PROPERTY(QColor itemNormalColor READ itemNormalColor WRITE setItemNormalColor NOTIFY itemNormalColorChanged) Q_PROPERTY(QColor itemNormalColor READ itemNormalColor WRITE setItemNormalColor NOTIFY itemNormalColorChanged)
Q_PROPERTY(QColor itemHoverColor READ itemHoverColor WRITE setItemHoverColor NOTIFY itemHoverColorChanged)
Q_PROPERTY(QColor windowBackgroundColor READ windowBackgroundColor WRITE setWindowBackgroundColor NOTIFY
windowBackgroundColorChanged)
Q_PROPERTY(QColor windowActiveBackgroundColor READ windowActiveBackgroundColor WRITE setWindowActiveBackgroundColor
NOTIFY windowActiveBackgroundColorChanged)
Q_PROPERTY(QString desktopImagePath READ desktopImagePath WRITE setDesktopImagePath NOTIFY desktopImagePathChanged)
Q_PROPERTY(bool blurBehindWindowEnabled READ blurBehindWindowEnabled WRITE setBlurBehindWindowEnabled NOTIFY
blurBehindWindowEnabledChanged)
public: public:
Theme(QObject *parent = nullptr); Theme(QObject *parent = nullptr);
@ -21,13 +31,39 @@ public:
QColor itemNormalColor() const; QColor itemNormalColor() const;
void setItemNormalColor(const QColor &color); void setItemNormalColor(const QColor &color);
QColor itemHoverColor() const;
void setItemHoverColor(const QColor &color);
QColor windowBackgroundColor() const;
void setWindowBackgroundColor(const QColor &color);
QColor windowActiveBackgroundColor() const;
void setWindowActiveBackgroundColor(const QColor &color);
QString desktopImagePath() const;
void setDesktopImagePath(const QString &path);
bool blurBehindWindowEnabled() const;
void setBlurBehindWindowEnabled(bool enabled);
signals: signals:
void fontPrimaryColorChanged(); void fontPrimaryColorChanged();
void itemNormalColorChanged(); void itemNormalColorChanged();
void windowBackgroundColorChanged();
void windowActiveBackgroundColorChanged();
void desktopImagePathChanged();
void blurBehindWindowEnabledChanged();
void itemHoverColorChanged();
private: private:
QColor m_fontPrimaryColor; QColor m_fontPrimaryColor;
QColor m_itemNormalColor; QColor m_itemNormalColor;
QColor m_itemHoverColor;
QColor m_windowBackgroundColor;
QColor m_windowActiveBackgroundColor;
QString m_desktopImagePath;
bool m_blurBehindWindowEnabled;
}; };
#endif // THEME_H #endif // THEME_H

72
Fluent/Utilities.cpp Normal file
View File

@ -0,0 +1,72 @@
#include "Utilities.h"
#include <QSettings>
Utilities *Utilities::instance() {
static Utilities *self = nullptr;
if (self == nullptr) {
self = new Utilities();
}
return self;
}
Utilities *Utilities::create(QQmlEngine *, QJSEngine *) {
auto ret = instance();
QJSEngine::setObjectOwnership(ret, QJSEngine::CppOwnership);
return ret;
}
Utilities::Utilities(QObject *parent) : QObject{parent} {
}
QRect Utilities::desktopAvailableGeometry(QQuickWindow *window) {
return window->screen()->availableGeometry();
}
QUrl Utilities::getUrlByFilePath(const QString &path) {
return QUrl::fromLocalFile(path);
}
bool Utilities::isMacos() {
#if defined(Q_OS_MACOS)
return true;
#else
return false;
#endif
}
bool Utilities::isWin() {
#if defined(Q_OS_WIN)
return true;
#else
return false;
#endif
}
int Utilities::windowBuildNumber() {
#if defined(Q_OS_WIN)
QSettings regKey{QString::fromUtf8(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)"),
QSettings::NativeFormat};
if (regKey.contains(QString::fromUtf8("CurrentBuildNumber"))) {
auto buildNumber = regKey.value(QString::fromUtf8("CurrentBuildNumber")).toInt();
return buildNumber;
}
#endif
return -1;
}
bool Utilities::isWindows11OrGreater() {
static QVariant var;
if (var.isNull()) {
#if defined(Q_OS_WIN)
auto buildNumber = windowBuildNumber();
if (buildNumber >= 22000) {
var = QVariant::fromValue(true);
return true;
}
#endif
var = QVariant::fromValue(false);
return false;
} else {
return var.toBool();
}
}

26
Fluent/Utilities.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef UTILITIES_H
#define UTILITIES_H
#include <QObject>
#include <QQmlEngine>
#include <QQuickWindow>
class Utilities : public QObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
public:
static Utilities *instance();
static Utilities *create(QQmlEngine *, QJSEngine *);
Q_INVOKABLE int windowBuildNumber();
Q_INVOKABLE bool isWindows11OrGreater();
Q_INVOKABLE bool isWin();
Q_INVOKABLE bool isMacos();
Q_INVOKABLE QRect desktopAvailableGeometry(QQuickWindow *window);
Q_INVOKABLE QUrl getUrlByFilePath(const QString &path);
protected:
Utilities(QObject *parent = nullptr);
};
#endif // UTILITIES_H

33
Fluent/qml/Acrylic.qml Normal file
View File

@ -0,0 +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
}
}

View File

@ -14,7 +14,11 @@ Quick.Rectangle {
property string maximizeText : qsTr("Maximize") property string maximizeText : qsTr("Maximize")
property Quick.color textColor: Theme.fontPrimaryColor property Quick.color textColor: Theme.fontPrimaryColor
property Quick.color maximizeNormalColor: Theme.itemNormalColor property Quick.color maximizeNormalColor: Theme.itemNormalColor
property Quick.color maximizeHoverColor: Theme.itemHoverColor
property bool isMac: Utilities.isMacos()
property alias buttonMaximize: btn_maximize property alias buttonMaximize: btn_maximize
property alias layoutMacosButtons: layout_macos_buttons
property alias layoutStandardbuttons: layout_standard_buttons
Quick.Item{ Quick.Item{
id:d id:d
@ -40,6 +44,11 @@ Quick.Rectangle {
} }
RowLayout { RowLayout {
id:layout_standard_buttons
height: parent.height
anchors.right: parent.right
spacing: 0
IconButton{ IconButton{
id:btn_maximize id:btn_maximize
property bool hover: btn_maximize.hovered property bool hover: btn_maximize.hovered
@ -65,5 +74,14 @@ Quick.Rectangle {
} }
} }
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
}
} }

19
Fluent/qml/Icon.qml Normal file
View File

@ -0,0 +1,19 @@
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"
}
}

245
Fluent/qml/InfoBar.qml Normal file
View File

@ -0,0 +1,245 @@
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
}
}

7
Fluent/qml/Object.qml Normal file
View File

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

21
Fluent/qml/Shadow.qml Normal file
View File

@ -0,0 +1,21 @@
import QtQuick
Item {
property color color: FluTheme.dark ? "#000000" : "#999999"
property int elevation: 5
property int radius: 4
id:control
anchors.fill: parent
Repeater{
model: elevation
Rectangle{
anchors.fill: parent
color: "#00000000"
opacity: 0.01 * (elevation-index+1)
anchors.margins: -index
radius: control.radius+index
border.width: index
border.color: control.color
}
}
}

View File

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

View File

@ -11,6 +11,14 @@ Quick.Window {
property bool showDark: false property bool showDark: false
property bool fixSize: false property bool fixSize: false
property bool stayTop: 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 { property Quick.Item appBar: AppBar {
title: root.title title: root.title
height: 30 height: 30
@ -39,4 +47,140 @@ Quick.Window {
Quick.Component.onCompleted: { Quick.Component.onCompleted: {
Router.addWindow(root) 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)
}
}
}
} }

BIN
Fluent/resources/noise.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB