diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index ba032ee58f..2c1504bbda 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 10e8a959d0..68460ef800 100644 --- a/src/qml/bitcoin.cpp +++ b/src/qml/bitcoin.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -243,6 +244,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}; #ifdef __ANDROID__ AndroidNotifier android_notifier{node_model}; @@ -278,6 +280,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 10e4db7f95..639f540718 100644 --- a/src/qml/components/ThemeSettings.qml +++ b/src/qml/components/ThemeSettings.qml @@ -21,13 +21,14 @@ ColumnLayout { header: qsTr("Light") actionItem: Icon { anchors.centerIn: parent - visible: !Theme.dark + visible: !Theme.manualDark && Theme.manualTheme source: "image://images/check" color: Theme.color.neutral9 size: 24 } onClicked: { - Theme.dark = false + Theme.manualTheme = true; + Theme.manualDark = false; } } Separator { Layout.fillWidth: true } @@ -36,13 +37,37 @@ ColumnLayout { header: qsTr("Dark") actionItem: Icon { anchors.centerIn: parent - visible: Theme.dark + visible: Theme.manualDark && Theme.manualTheme source: "image://images/check" color: Theme.color.neutral9 size: 24 } 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: Icon { + anchors.centerIn: parent + visible: !Theme.manualTheme + source: "image://images/check" + size: 24 + } + 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