From bc753859b5ab5d1402940024c0478548165b57f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 6 Nov 2016 04:29:12 +0100 Subject: [PATCH] GH-378 add basic custom theme support Files you can customize are created in themes/custom/ --- api/logic/Json.h | 14 +- application/CMakeLists.txt | 3 + application/MultiMC.cpp | 5 +- application/themes/BrightTheme.cpp | 11 +- application/themes/BrightTheme.h | 2 + application/themes/CustomTheme.cpp | 227 +++++++++++++++++++++++++++++ application/themes/CustomTheme.h | 28 ++++ application/themes/DarkTheme.cpp | 11 +- application/themes/DarkTheme.h | 2 + application/themes/FusionTheme.cpp | 26 ---- application/themes/FusionTheme.h | 3 - application/themes/ITheme.cpp | 26 ++++ application/themes/ITheme.h | 4 + application/themes/SystemTheme.cpp | 10 ++ application/themes/SystemTheme.h | 3 +- 15 files changed, 335 insertions(+), 40 deletions(-) create mode 100644 application/themes/CustomTheme.cpp create mode 100644 application/themes/CustomTheme.h create mode 100644 application/themes/ITheme.cpp diff --git a/api/logic/Json.h b/api/logic/Json.h index 2cb60f0e5..e59b25de3 100644 --- a/api/logic/Json.h +++ b/api/logic/Json.h @@ -23,16 +23,16 @@ public: }; /// @throw FileSystemException -void write(const QJsonDocument &doc, const QString &filename); +MULTIMC_LOGIC_EXPORT void write(const QJsonDocument &doc, const QString &filename); /// @throw FileSystemException -void write(const QJsonObject &object, const QString &filename); +MULTIMC_LOGIC_EXPORT void write(const QJsonObject &object, const QString &filename); /// @throw FileSystemException -void write(const QJsonArray &array, const QString &filename); +MULTIMC_LOGIC_EXPORT void write(const QJsonArray &array, const QString &filename); -QByteArray toBinary(const QJsonObject &obj); -QByteArray toBinary(const QJsonArray &array); -QByteArray toText(const QJsonObject &obj); -QByteArray toText(const QJsonArray &array); +MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonObject &obj); +MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonArray &array); +MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonObject &obj); +MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonArray &array); /// @throw JsonException MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document"); diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index 5ac7001dd..f89de0cbd 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -111,8 +111,11 @@ SET(MULTIMC_SOURCES themes/FusionTheme.h themes/BrightTheme.cpp themes/BrightTheme.h + themes/CustomTheme.cpp + themes/CustomTheme.h themes/DarkTheme.cpp themes/DarkTheme.h + themes/ITheme.cpp themes/ITheme.h themes/SystemTheme.cpp themes/SystemTheme.h diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp index 969e54e51..9ccd08959 100644 --- a/application/MultiMC.cpp +++ b/application/MultiMC.cpp @@ -15,6 +15,7 @@ #include "themes/SystemTheme.h" #include "themes/DarkTheme.h" #include "themes/BrightTheme.h" +#include "themes/CustomTheme.h" #include #include @@ -1010,9 +1011,11 @@ void MultiMC::initThemes() { m_themes.insert(std::make_pair(theme->id(), std::unique_ptr(theme))); }; + auto darkTheme = new DarkTheme(); insertTheme(new SystemTheme()); - insertTheme(new DarkTheme()); + insertTheme(darkTheme); insertTheme(new BrightTheme()); + insertTheme(new CustomTheme(darkTheme, "custom")); } void MultiMC::setApplicationTheme(const QString& name) diff --git a/application/themes/BrightTheme.cpp b/application/themes/BrightTheme.cpp index 83d8f0b21..a7cfa0107 100644 --- a/application/themes/BrightTheme.cpp +++ b/application/themes/BrightTheme.cpp @@ -26,9 +26,18 @@ QPalette BrightTheme::colorScheme() brightPalette.setColor(QPalette::Link, QColor(41, 128, 185)); brightPalette.setColor(QPalette::Highlight, QColor(61, 174, 233)); brightPalette.setColor(QPalette::HighlightedText, QColor(239,240,241)); - return fadeInactive(brightPalette, 0.5f, QColor(239,240,241)); + return fadeInactive(brightPalette, fadeAmount(), fadeColor()); } +double BrightTheme::fadeAmount() +{ + return 0.5; +} + +QColor BrightTheme::fadeColor() +{ + return QColor(239,240,241); +} QString BrightTheme::appStyleSheet() { diff --git a/application/themes/BrightTheme.h b/application/themes/BrightTheme.h index 85b7041b9..814aa6de2 100644 --- a/application/themes/BrightTheme.h +++ b/application/themes/BrightTheme.h @@ -11,5 +11,7 @@ public: QString name() override; QString appStyleSheet() override; QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; }; diff --git a/application/themes/CustomTheme.cpp b/application/themes/CustomTheme.cpp new file mode 100644 index 000000000..a31415738 --- /dev/null +++ b/application/themes/CustomTheme.cpp @@ -0,0 +1,227 @@ +#include "CustomTheme.h" +#include +#include +#include + +const char * themeFile = "theme.json"; +const char * styleFile = "themeStyle.css"; + +static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAmount, QColor &fadeColor, QString &name, QString &widgets) +{ + QFileInfo pathInfo(path); + if(pathInfo.exists() && pathInfo.isFile()) + { + try + { + auto doc = Json::requireDocument(path, "Theme JSON file"); + const QJsonObject root = doc.object(); + name = Json::requireString(root, "name", "Theme name"); + widgets = Json::requireString(root, "widgets", "Qt widget theme"); + auto colorsRoot = Json::requireObject(root, "colors", "colors object"); + auto readColor = [&](QString colorName) -> QColor + { + auto colorValue = Json::ensureString(colorsRoot, colorName, QString()); + if(!colorValue.isEmpty()) + { + QColor color(colorValue); + if(!color.isValid()) + { + qWarning() << "Color value" << colorValue << "for" << colorName << "was not recognized."; + return QColor(); + } + return color; + } + return QColor(); + }; + auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) + { + auto color = readColor(colorName); + if(color.isValid()) + { + palette.setColor(role, color); + } + else + { + qDebug() << "Color value for" << colorName << "was not present."; + } + }; + + // palette + readAndSetColor(QPalette::Window, "Window"); + readAndSetColor(QPalette::WindowText, "WindowText"); + readAndSetColor(QPalette::Base, "Base"); + readAndSetColor(QPalette::AlternateBase, "AlternateBase"); + readAndSetColor(QPalette::ToolTipBase, "ToolTipBase"); + readAndSetColor(QPalette::ToolTipText, "ToolTipText"); + readAndSetColor(QPalette::Text, "Text"); + readAndSetColor(QPalette::Button, "Button"); + readAndSetColor(QPalette::ButtonText, "ButtonText"); + readAndSetColor(QPalette::BrightText, "BrightText"); + readAndSetColor(QPalette::Link, "Link"); + readAndSetColor(QPalette::Highlight, "Highlight"); + readAndSetColor(QPalette::HighlightedText, "HighlightedText"); + + //fade + fadeColor = readColor("fadeColor"); + fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount"); + + } + catch(Exception e) + { + qWarning() << "Couldn't load theme json: " << e.cause(); + return false; + } + } + else + { + qDebug() << "No theme json present."; + return false; + } + return true; +} + +static bool writeThemeJson(const QString &path, const QPalette &palette, double fadeAmount, QColor fadeColor, QString name, QString widgets) +{ + QJsonObject rootObj; + rootObj.insert("name", name); + rootObj.insert("widgets", widgets); + + QJsonObject colorsObj; + auto insertColor = [&](QPalette::ColorRole role, QString colorName) + { + colorsObj.insert(colorName, palette.color(role).name()); + }; + + // palette + insertColor(QPalette::Window, "Window"); + insertColor(QPalette::WindowText, "WindowText"); + insertColor(QPalette::Base, "Base"); + insertColor(QPalette::AlternateBase, "AlternateBase"); + insertColor(QPalette::ToolTipBase, "ToolTipBase"); + insertColor(QPalette::ToolTipText, "ToolTipText"); + insertColor(QPalette::Text, "Text"); + insertColor(QPalette::Button, "Button"); + insertColor(QPalette::ButtonText, "ButtonText"); + insertColor(QPalette::BrightText, "BrightText"); + insertColor(QPalette::Link, "Link"); + insertColor(QPalette::Highlight, "Highlight"); + insertColor(QPalette::HighlightedText, "HighlightedText"); + + // fade + colorsObj.insert("fadeColor", fadeColor.name()); + colorsObj.insert("fadeAmount", fadeAmount); + + rootObj.insert("colors", colorsObj); + try + { + Json::write(rootObj, path); + return true; + } + catch (Exception e) + { + qWarning() << "Failed to write theme json to" << path; + return false; + } +} + +CustomTheme::CustomTheme(ITheme* baseTheme, QString folder) +{ + m_id = folder; + QString path = FS::PathCombine("themes", m_id); + + qDebug() << "Loading theme" << m_id; + + if(!FS::ensureFolderPathExists(path)) + { + qWarning() << "couldn't create folder for theme!"; + m_palette = baseTheme->colorScheme(); + m_styleSheet = baseTheme->appStyleSheet(); + return; + } + + auto themeFilePath = FS::PathCombine(path, themeFile); + + m_palette = baseTheme->colorScheme(); + if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets)) + { + m_name = "Custom"; + m_palette = baseTheme->colorScheme(); + m_fadeColor = baseTheme->fadeColor(); + m_fadeAmount = baseTheme->fadeAmount(); + m_widgets = baseTheme->qtTheme(); + + QFileInfo info(themeFilePath); + if(!info.exists()) + { + writeThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, "Custom", m_widgets); + } + } + else + { + m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); + } + + auto cssFilePath = FS::PathCombine(path, styleFile); + QFileInfo info (cssFilePath); + if(info.isFile()) + { + try + { + // TODO: validate css? + m_styleSheet = QString::fromUtf8(FS::read(cssFilePath)); + } + catch(Exception e) + { + qWarning() << "Couldn't load css:" << e.cause() << "from" << cssFilePath; + m_styleSheet = baseTheme->appStyleSheet(); + } + } + else + { + qDebug() << "No theme css present."; + m_styleSheet = baseTheme->appStyleSheet(); + try + { + FS::write(cssFilePath, m_styleSheet.toUtf8()); + } + catch(Exception e) + { + qWarning() << "Couldn't write css:" << e.cause() << "to" << cssFilePath; + } + } +} + +QString CustomTheme::id() +{ + return m_id; +} + +QString CustomTheme::name() +{ + return m_name; +} + +QPalette CustomTheme::colorScheme() +{ + return m_palette; +} + +QString CustomTheme::appStyleSheet() +{ + return m_styleSheet; +} + +double CustomTheme::fadeAmount() +{ + return m_fadeAmount; +} + +QColor CustomTheme::fadeColor() +{ + return m_fadeColor; +} + +QString CustomTheme::qtTheme() +{ + return m_widgets; +} diff --git a/application/themes/CustomTheme.h b/application/themes/CustomTheme.h new file mode 100644 index 000000000..94cd10a5f --- /dev/null +++ b/application/themes/CustomTheme.h @@ -0,0 +1,28 @@ +#pragma once + +#include "ITheme.h" + +class CustomTheme: public ITheme +{ +public: + CustomTheme(ITheme * baseTheme, QString folder); + virtual ~CustomTheme() {} + + QString id() override; + QString name() override; + QString appStyleSheet() override; + QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; + QString qtTheme() override; + +private: /* data */ + QPalette m_palette; + QColor m_fadeColor; + double m_fadeAmount; + QString m_styleSheet; + QString m_name; + QString m_id; + QString m_widgets; +}; + diff --git a/application/themes/DarkTheme.cpp b/application/themes/DarkTheme.cpp index 143dd5336..2bcb4cd7b 100644 --- a/application/themes/DarkTheme.cpp +++ b/application/themes/DarkTheme.cpp @@ -26,9 +26,18 @@ QPalette DarkTheme::colorScheme() darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); darkPalette.setColor(QPalette::HighlightedText, Qt::black); - return fadeInactive(darkPalette, 0.5f, QColor(49,54,59)); + return fadeInactive(darkPalette, fadeAmount(), fadeColor()); } +double DarkTheme::fadeAmount() +{ + return 0.5; +} + +QColor DarkTheme::fadeColor() +{ + return QColor(49,54,59); +} QString DarkTheme::appStyleSheet() { diff --git a/application/themes/DarkTheme.h b/application/themes/DarkTheme.h index a8c17de77..142f7860e 100644 --- a/application/themes/DarkTheme.h +++ b/application/themes/DarkTheme.h @@ -11,4 +11,6 @@ public: QString name() override; QString appStyleSheet() override; QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; }; diff --git a/application/themes/FusionTheme.cpp b/application/themes/FusionTheme.cpp index fa2ccf64d..aaf7b3d59 100644 --- a/application/themes/FusionTheme.cpp +++ b/application/themes/FusionTheme.cpp @@ -1,32 +1,6 @@ #include "FusionTheme.h" -#include "rainbow.h" QString FusionTheme::qtTheme() { return "Fusion"; } - -QPalette FusionTheme::fadeInactive(QPalette in, qreal bias, QColor color) -{ - auto blend = [&in, bias, color](QPalette::ColorRole role) - { - QColor from = in.color(QPalette::Active, role); - QColor blended = Rainbow::mix(from, color, bias); - in.setColor(QPalette::Disabled, role, blended); - }; - blend(QPalette::Window); - blend(QPalette::WindowText); - blend(QPalette::Base); - blend(QPalette::AlternateBase); - blend(QPalette::ToolTipBase); - blend(QPalette::ToolTipText); - blend(QPalette::Text); - blend(QPalette::Button); - blend(QPalette::ButtonText); - blend(QPalette::BrightText); - blend(QPalette::Link); - blend(QPalette::Highlight); - blend(QPalette::HighlightedText); - return in; -} - diff --git a/application/themes/FusionTheme.h b/application/themes/FusionTheme.h index 2f938eb94..e2d9014c5 100644 --- a/application/themes/FusionTheme.h +++ b/application/themes/FusionTheme.h @@ -8,7 +8,4 @@ public: virtual ~FusionTheme() {} QString qtTheme() override; - -protected: - QPalette fadeInactive(QPalette in, qreal bias, QColor color); }; diff --git a/application/themes/ITheme.cpp b/application/themes/ITheme.cpp new file mode 100644 index 000000000..aefcc381f --- /dev/null +++ b/application/themes/ITheme.cpp @@ -0,0 +1,26 @@ +#include "ITheme.h" +#include "rainbow.h" + +QPalette ITheme::fadeInactive(QPalette in, qreal bias, QColor color) +{ + auto blend = [&in, bias, color](QPalette::ColorRole role) + { + QColor from = in.color(QPalette::Active, role); + QColor blended = Rainbow::mix(from, color, bias); + in.setColor(QPalette::Disabled, role, blended); + }; + blend(QPalette::Window); + blend(QPalette::WindowText); + blend(QPalette::Base); + blend(QPalette::AlternateBase); + blend(QPalette::ToolTipBase); + blend(QPalette::ToolTipText); + blend(QPalette::Text); + blend(QPalette::Button); + blend(QPalette::ButtonText); + blend(QPalette::BrightText); + blend(QPalette::Link); + blend(QPalette::Highlight); + blend(QPalette::HighlightedText); + return in; +} diff --git a/application/themes/ITheme.h b/application/themes/ITheme.h index 969969bec..9c071e0a1 100644 --- a/application/themes/ITheme.h +++ b/application/themes/ITheme.h @@ -11,4 +11,8 @@ public: virtual QString appStyleSheet() = 0; virtual QString qtTheme() = 0; virtual QPalette colorScheme() = 0; + virtual QColor fadeColor() = 0; + virtual double fadeAmount() = 0; + + static QPalette fadeInactive(QPalette in, qreal bias, QColor color); }; diff --git a/application/themes/SystemTheme.cpp b/application/themes/SystemTheme.cpp index 6d8a0ee87..f7a680616 100644 --- a/application/themes/SystemTheme.cpp +++ b/application/themes/SystemTheme.cpp @@ -47,3 +47,13 @@ QString SystemTheme::appStyleSheet() { return QString(); } + +double SystemTheme::fadeAmount() + { + return 0.5; + } + +QColor SystemTheme::fadeColor() + { + return QColor(128,128,128); + } diff --git a/application/themes/SystemTheme.h b/application/themes/SystemTheme.h index 75a8738c4..e4a84bca0 100644 --- a/application/themes/SystemTheme.h +++ b/application/themes/SystemTheme.h @@ -13,8 +13,9 @@ public: QString qtTheme() override; QString appStyleSheet() override; QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; private: QPalette systemPalette; QString systemTheme; }; -