diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 525199c045..0a566e9e87 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -43,6 +43,7 @@ QT_MOC_CPP = \ qml/models/moc_options_model.cpp \ qml/models/moc_peerlistsortproxy.cpp \ qml/moc_appmode.cpp \ + qml/moc_thememanager.cpp \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ qt/moc_askpassphrasedialog.cpp \ @@ -124,6 +125,7 @@ BITCOIN_QT_H = \ qml/appmode.h \ qml/bitcoin.h \ qml/imageprovider.h \ + qml/thememanager.h \ qml/util.h \ qt/addressbookpage.h \ qt/addresstablemodel.h \ @@ -309,6 +311,7 @@ BITCOIN_QML_BASE_CPP = \ qml/models/options_model.cpp \ qml/models/peerlistsortproxy.cpp \ qml/imageprovider.cpp \ + qml/thememanager.cpp \ qml/util.cpp QML_RES_FONTS = \ diff --git a/src/qml/bitcoin.cpp b/src/qml/bitcoin.cpp index 065e9b9bdd..c38b659d10 100644 --- a/src/qml/bitcoin.cpp +++ b/src/qml/bitcoin.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -232,6 +233,7 @@ int QmlGuiMain(int argc, char* argv[]) QObject::connect(&init_executor, &InitExecutor::shutdownResult, qGuiApp, &QGuiApplication::quit, Qt::QueuedConnection); // QObject::connect(&init_executor, &InitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException); + ThemeManager theme_manager{}; NetworkTrafficTower network_traffic_tower{node_model}; ChainModel chain_model{*chain}; @@ -264,6 +266,7 @@ int QmlGuiMain(int argc, char* argv[]) engine.rootContext()->setContextProperty("chainModel", &chain_model); engine.rootContext()->setContextProperty("peerTableModel", &peer_model); engine.rootContext()->setContextProperty("peerListModelProxy", &peer_model_sort_proxy); + engine.rootContext()->setContextProperty("themeManager", &theme_manager); OptionsQmlModel options_model{*node}; engine.rootContext()->setContextProperty("optionsModel", &options_model); diff --git a/src/qml/components/ThemeSettings.qml b/src/qml/components/ThemeSettings.qml index dd052ec268..9dc582717e 100644 --- a/src/qml/components/ThemeSettings.qml +++ b/src/qml/components/ThemeSettings.qml @@ -21,7 +21,7 @@ ColumnLayout { header: qsTr("Light") actionItem: Button { anchors.centerIn: parent - visible: !Theme.dark + visible: !Theme.manualDark && Theme.manualTheme icon.source: "image://images/check" icon.color: Theme.color.neutral9 icon.height: 24 @@ -33,7 +33,8 @@ ColumnLayout { } } onClicked: { - Theme.dark = false + Theme.manualTheme = true; + Theme.manualDark = false; } } Separator { Layout.fillWidth: true } @@ -42,7 +43,7 @@ ColumnLayout { header: qsTr("Dark") actionItem: Button { anchors.centerIn: parent - visible: Theme.dark + visible: Theme.manualDark && Theme.manualTheme icon.source: "image://images/check" icon.color: Theme.color.neutral9 icon.height: 24 @@ -54,7 +55,38 @@ ColumnLayout { } } onClicked: { - Theme.dark = true; + Theme.manualTheme = true; + Theme.manualDark = true; + } + } + Separator { Layout.fillWidth: true } + Setting { + id: systemThemeSetting + property bool systemThemeAvailable: Theme.systemThemeAvailable + Layout.fillWidth: true + header: qsTr("System") + actionItem: Button { + anchors.centerIn: parent + visible: !Theme.manualTheme + icon.source: "image://images/check" + icon.color: Theme.color.neutral9 + icon.height: 24 + icon.width: 24 + background: null + + Behavior on icon.color { + ColorAnimation { duration: 150 } + } + } + Component.onCompleted: { + if (systemThemeAvailable) { + systemThemeSetting.state = "FILLED" + } else { + systemThemeSetting.state = "DISABLED" + } + } + onClicked: { + Theme.manualTheme = false } } } diff --git a/src/qml/controls/Theme.qml b/src/qml/controls/Theme.qml index df62e0a4ad..d6913bceee 100644 --- a/src/qml/controls/Theme.qml +++ b/src/qml/controls/Theme.qml @@ -3,9 +3,14 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import Qt.labs.settings 1.0 +import org.bitcoincore.qt 1.0 + Control { id: root - property bool dark: true + property bool dark: manualTheme ? manualDark : themeManager.darkMode + property bool systemThemeAvailable: themeManager.systemThemeAvailable + property bool manualTheme: false + property bool manualDark: true property real blockclocksize: (5/12) readonly property ColorSet color: dark ? darkColorSet : lightColorSet readonly property ImageSet image: dark ? darkImageSet : lightImageSet @@ -13,9 +18,23 @@ Control { Settings { id: settings property alias dark: root.dark + property alias manualTheme: root.manualTheme + property alias manualDark: root.manualDark property alias blockclocksize: root.blockclocksize } + SystemPalette { + id: systemColor + + onBaseChanged: { + themeManager.systemBaseColor = systemColor.base + } + } + + Component.onCompleted: { + themeManager.systemBaseColor = systemColor.base + } + component ColorSet: QtObject { required property color white required property color background diff --git a/src/qml/thememanager.cpp b/src/qml/thememanager.cpp new file mode 100644 index 0000000000..3c6c420d8d --- /dev/null +++ b/src/qml/thememanager.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +ThemeManager::ThemeManager(QObject* parent) + : QObject(parent) +{ +} + +void ThemeManager::setSystemBaseColor(QColor base_color) +{ + // Convert QColor's 8-bit RGB values to linear RGB values + double linearR = base_color.redF(); + double linearG = base_color.greenF(); + double linearB = base_color.blueF(); + + // Constants for the luminance formula + const double RED_FACTOR = 0.2126; + const double GREEN_FACTOR = 0.7152; + const double BLUE_FACTOR = 0.0722; + + // Calculate luminance using the formula + double luminance = RED_FACTOR * linearR + GREEN_FACTOR * linearG + BLUE_FACTOR * linearB; + + if (luminance <= 0.5) { + m_dark_mode = true; + } else { + m_dark_mode = false; + } + + m_system_base_color = base_color; + + #ifdef Q_OS_MAC + setSystemThemeAvailable(true); + #endif + + Q_EMIT darkModeChanged(); +} + +void ThemeManager::setSystemThemeAvailable(bool available) +{ + if (m_system_theme_available != available) { + m_system_theme_available = available; + Q_EMIT systemThemeAvailableChanged(); + } +} \ No newline at end of file diff --git a/src/qml/thememanager.h b/src/qml/thememanager.h new file mode 100644 index 0000000000..05d095c1ce --- /dev/null +++ b/src/qml/thememanager.h @@ -0,0 +1,41 @@ +// Copyright (c) 2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QML_THEMEMANAGER_H +#define BITCOIN_QML_THEMEMANAGER_H + +#include +#include +#include + + +class ThemeManager : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool darkMode READ darkMode NOTIFY darkModeChanged) + Q_PROPERTY(QColor systemBaseColor READ systemBaseColor WRITE setSystemBaseColor) + Q_PROPERTY(bool systemThemeAvailable READ systemThemeAvailable WRITE setSystemThemeAvailable NOTIFY systemThemeAvailableChanged) + +public: + explicit ThemeManager(QObject* parent = nullptr); + + bool darkMode() const { return m_dark_mode; }; + QColor systemBaseColor() const { return m_system_base_color; }; + bool systemThemeAvailable() const { return m_system_theme_available; }; + +public Q_SLOTS: + void setSystemBaseColor(QColor base_color); + void setSystemThemeAvailable(bool available); + +Q_SIGNALS: + void darkModeChanged(); + void systemThemeAvailableChanged(); + +private: + bool m_dark_mode{ true }; + QColor m_system_base_color; + bool m_system_theme_available{ false }; +}; + +#endif // BITCOIN_QML_THEMEMANAGER_H \ No newline at end of file