Merge pull request #149 from TayouVR/better-custom-themes

This commit is contained in:
Sefa Eyeoglu 2022-11-05 11:16:41 +01:00 committed by GitHub
commit e4bd82190a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 456 additions and 218 deletions

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
* Copyright (C) 2022 Tayou <tayou@gmx.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -54,12 +55,6 @@
#include "ui/pages/global/APIPage.h" #include "ui/pages/global/APIPage.h"
#include "ui/pages/global/CustomCommandsPage.h" #include "ui/pages/global/CustomCommandsPage.h"
#include "ui/themes/ITheme.h"
#include "ui/themes/SystemTheme.h"
#include "ui/themes/DarkTheme.h"
#include "ui/themes/BrightTheme.h"
#include "ui/themes/CustomTheme.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include "ui/WinDarkmode.h" #include "ui/WinDarkmode.h"
#include <versionhelpers.h> #include <versionhelpers.h>
@ -74,6 +69,8 @@
#include "ui/pagedialog/PageDialog.h" #include "ui/pagedialog/PageDialog.h"
#include "ui/themes/ThemeManager.h"
#include "ApplicationMessage.h" #include "ApplicationMessage.h"
#include <iostream> #include <iostream>
@ -750,29 +747,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Instance icons intialized."; qDebug() << "<> Instance icons intialized.";
} }
// Icon themes // Themes
{ m_themeManager = std::make_unique<ThemeManager>(m_mainWindow);
// TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies!
// set icon theme search path!
auto searchPaths = QIcon::themeSearchPaths();
searchPaths.append("iconthemes");
QIcon::setThemeSearchPaths(searchPaths);
qDebug() << "<> Icon themes initialized.";
}
// Initialize widget themes
{
auto insertTheme = [this](ITheme * theme)
{
m_themes.insert(std::make_pair(theme->id(), std::unique_ptr<ITheme>(theme)));
};
auto darkTheme = new DarkTheme();
insertTheme(new SystemTheme());
insertTheme(darkTheme);
insertTheme(new BrightTheme());
insertTheme(new CustomTheme(darkTheme, "custom"));
qDebug() << "<> Widget themes initialized.";
}
// initialize and load all instances // initialize and load all instances
{ {
@ -1126,45 +1102,19 @@ std::shared_ptr<JavaInstallList> Application::javalist()
return m_javalist; return m_javalist;
} }
std::vector<ITheme *> Application::getValidApplicationThemes() QList<ITheme*> Application::getValidApplicationThemes()
{ {
std::vector<ITheme *> ret; return m_themeManager->getValidApplicationThemes();
auto iter = m_themes.cbegin();
while (iter != m_themes.cend())
{
ret.push_back((*iter).second.get());
iter++;
}
return ret;
} }
void Application::setApplicationTheme(const QString& name, bool initial) void Application::setApplicationTheme(const QString& name, bool initial)
{ {
auto systemPalette = qApp->palette(); m_themeManager->setApplicationTheme(name, initial);
auto themeIter = m_themes.find(name);
if(themeIter != m_themes.end())
{
auto & theme = (*themeIter).second;
theme->apply(initial);
#ifdef Q_OS_WIN
if (m_mainWindow && IsWindows10OrGreater()) {
if (QString::compare(theme->id(), "dark") == 0) {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
} else {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
}
}
#endif
}
else
{
qWarning() << "Tried to set invalid theme:" << name;
}
} }
void Application::setIconTheme(const QString& name) void Application::setIconTheme(const QString& name)
{ {
QIcon::setThemeName(name); m_themeManager->setIconTheme(name);
} }
QIcon Application::getThemedIcon(const QString& name) QIcon Application::getThemedIcon(const QString& name)

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Tayou <tayou@gmx.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -68,6 +69,7 @@ class BaseDetachedToolFactory;
class TranslationsModel; class TranslationsModel;
class ITheme; class ITheme;
class MCEditTool; class MCEditTool;
class ThemeManager;
namespace Meta { namespace Meta {
class Index; class Index;
@ -118,7 +120,7 @@ public:
void setIconTheme(const QString& name); void setIconTheme(const QString& name);
std::vector<ITheme *> getValidApplicationThemes(); QList<ITheme*> getValidApplicationThemes();
void setApplicationTheme(const QString& name, bool initial); void setApplicationTheme(const QString& name, bool initial);
@ -255,9 +257,9 @@ private:
std::shared_ptr<JavaInstallList> m_javalist; std::shared_ptr<JavaInstallList> m_javalist;
std::shared_ptr<TranslationsModel> m_translations; std::shared_ptr<TranslationsModel> m_translations;
std::shared_ptr<GenericPageProvider> m_globalSettingsProvider; std::shared_ptr<GenericPageProvider> m_globalSettingsProvider;
std::map<QString, std::unique_ptr<ITheme>> m_themes;
std::unique_ptr<MCEditTool> m_mcedit; std::unique_ptr<MCEditTool> m_mcedit;
QSet<QString> m_features; QSet<QString> m_features;
std::unique_ptr<ThemeManager> m_themeManager;
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers; QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;

View File

@ -647,6 +647,8 @@ SET(LAUNCHER_SOURCES
ui/themes/ITheme.h ui/themes/ITheme.h
ui/themes/SystemTheme.cpp ui/themes/SystemTheme.cpp
ui/themes/SystemTheme.h ui/themes/SystemTheme.h
ui/themes/ThemeManager.cpp
ui/themes/ThemeManager.h
# Processes # Processes
LaunchController.h LaunchController.h

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (c) 2022 dada513 <dada513@protonmail.com> * Copyright (c) 2022 dada513 <dada513@protonmail.com>
* Copyright (C) 2022 Tayou <tayou@gmx.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -396,46 +397,8 @@ void LauncherPage::loadSettings()
m_currentUpdateChannel = s->get("UpdateChannel").toString(); m_currentUpdateChannel = s->get("UpdateChannel").toString();
//FIXME: make generic //FIXME: make generic
auto theme = s->get("IconTheme").toString(); auto theme = s->get("IconTheme").toString();
if (theme == "pe_colored") QStringList iconThemeOptions{"pe_colored", "pe_light", "pe_dark", "pe_blue", "OSX", "iOS", "flat", "flat_white", "multimc", "custom"};
{ ui->themeComboBox->setCurrentIndex(iconThemeOptions.indexOf(theme));
ui->themeComboBox->setCurrentIndex(0);
}
else if (theme == "pe_light")
{
ui->themeComboBox->setCurrentIndex(1);
}
else if (theme == "pe_dark")
{
ui->themeComboBox->setCurrentIndex(2);
}
else if (theme == "pe_blue")
{
ui->themeComboBox->setCurrentIndex(3);
}
else if (theme == "OSX")
{
ui->themeComboBox->setCurrentIndex(4);
}
else if (theme == "iOS")
{
ui->themeComboBox->setCurrentIndex(5);
}
else if (theme == "flat")
{
ui->themeComboBox->setCurrentIndex(6);
}
else if (theme == "flat_white")
{
ui->themeComboBox->setCurrentIndex(7);
}
else if (theme == "multimc")
{
ui->themeComboBox->setCurrentIndex(8);
}
else if (theme == "custom")
{
ui->themeComboBox->setCurrentIndex(9);
}
auto cat = s->get("BackgroundCat").toString(); auto cat = s->get("BackgroundCat").toString();
if (cat == "kitteh") { if (cat == "kitteh") {

View File

@ -1,48 +1,81 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Tayou <tayou@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "CustomTheme.h" #include "CustomTheme.h"
#include <QDir>
#include <Json.h>
#include <FileSystem.h> #include <FileSystem.h>
#include <Json.h>
#include "ThemeManager.h"
const char * themeFile = "theme.json"; 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) static bool readThemeJson(const QString& path,
QPalette& palette,
double& fadeAmount,
QColor& fadeColor,
QString& name,
QString& widgets,
QString& qssFilePath,
bool& dataIncomplete)
{ {
QFileInfo pathInfo(path); QFileInfo pathInfo(path);
if(pathInfo.exists() && pathInfo.isFile()) if (pathInfo.exists() && pathInfo.isFile()) {
{ try {
try
{
auto doc = Json::requireDocument(path, "Theme JSON file"); auto doc = Json::requireDocument(path, "Theme JSON file");
const QJsonObject root = doc.object(); const QJsonObject root = doc.object();
dataIncomplete = !root.contains("qssFilePath");
name = Json::requireString(root, "name", "Theme name"); name = Json::requireString(root, "name", "Theme name");
widgets = Json::requireString(root, "widgets", "Qt widget theme"); widgets = Json::requireString(root, "widgets", "Qt widget theme");
qssFilePath = Json::ensureString(root, "qssFilePath", "themeStyle.css");
auto colorsRoot = Json::requireObject(root, "colors", "colors object"); auto colorsRoot = Json::requireObject(root, "colors", "colors object");
auto readColor = [&](QString colorName) -> QColor auto readColor = [&](QString colorName) -> QColor {
{
auto colorValue = Json::ensureString(colorsRoot, colorName, QString()); auto colorValue = Json::ensureString(colorsRoot, colorName, QString());
if(!colorValue.isEmpty()) if (!colorValue.isEmpty()) {
{
QColor color(colorValue); QColor color(colorValue);
if(!color.isValid()) if (!color.isValid()) {
{ themeWarningLog() << "Color value" << colorValue << "for" << colorName << "was not recognized.";
qWarning() << "Color value" << colorValue << "for" << colorName << "was not recognized.";
return QColor(); return QColor();
} }
return color; return color;
} }
return QColor(); return QColor();
}; };
auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) {
{
auto color = readColor(colorName); auto color = readColor(colorName);
if(color.isValid()) if (color.isValid()) {
{
palette.setColor(role, color); palette.setColor(role, color);
} } else {
else themeDebugLog() << "Color value for" << colorName << "was not present.";
{
qDebug() << "Color value for" << colorName << "was not present.";
} }
}; };
@ -61,36 +94,36 @@ static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAm
readAndSetColor(QPalette::Highlight, "Highlight"); readAndSetColor(QPalette::Highlight, "Highlight");
readAndSetColor(QPalette::HighlightedText, "HighlightedText"); readAndSetColor(QPalette::HighlightedText, "HighlightedText");
//fade // fade
fadeColor = readColor("fadeColor"); fadeColor = readColor("fadeColor");
fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount"); fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount");
} } catch (const Exception& e) {
catch (const Exception &e) themeWarningLog() << "Couldn't load theme json: " << e.cause();
{
qWarning() << "Couldn't load theme json: " << e.cause();
return false; return false;
} }
} } else {
else themeDebugLog() << "No theme json present.";
{
qDebug() << "No theme json present.";
return false; return false;
} }
return true; return true;
} }
static bool writeThemeJson(const QString &path, const QPalette &palette, double fadeAmount, QColor fadeColor, QString name, QString widgets) static bool writeThemeJson(const QString& path,
const QPalette& palette,
double fadeAmount,
QColor fadeColor,
QString name,
QString widgets,
QString qssFilePath)
{ {
QJsonObject rootObj; QJsonObject rootObj;
rootObj.insert("name", name); rootObj.insert("name", name);
rootObj.insert("widgets", widgets); rootObj.insert("widgets", widgets);
rootObj.insert("qssFilePath", qssFilePath);
QJsonObject colorsObj; QJsonObject colorsObj;
auto insertColor = [&](QPalette::ColorRole role, QString colorName) auto insertColor = [&](QPalette::ColorRole role, QString colorName) { colorsObj.insert(colorName, palette.color(role).name()); };
{
colorsObj.insert(colorName, palette.color(role).name());
};
// palette // palette
insertColor(QPalette::Window, "Window"); insertColor(QPalette::Window, "Window");
@ -112,82 +145,95 @@ static bool writeThemeJson(const QString &path, const QPalette &palette, double
colorsObj.insert("fadeAmount", fadeAmount); colorsObj.insert("fadeAmount", fadeAmount);
rootObj.insert("colors", colorsObj); rootObj.insert("colors", colorsObj);
try try {
{
Json::write(rootObj, path); Json::write(rootObj, path);
return true; return true;
} } catch (const Exception& e) {
catch (const Exception &e) themeWarningLog() << "Failed to write theme json to" << path;
{
qWarning() << "Failed to write theme json to" << path;
return false; return false;
} }
} }
CustomTheme::CustomTheme(ITheme* baseTheme, QString folder) /// @param baseTheme Base Theme
/// @param fileInfo FileInfo object for file to load
/// @param isManifest whether to load a theme manifest or a qss file
CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest)
{ {
m_id = folder; if (isManifest) {
QString path = FS::PathCombine("themes", m_id); m_id = fileInfo.dir().dirName();
QString pathResources = FS::PathCombine("themes", m_id, "resources");
qDebug() << "Loading theme" << m_id; QString path = FS::PathCombine("themes", m_id);
QString pathResources = FS::PathCombine("themes", m_id, "resources");
if(!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) if (!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) {
{ themeWarningLog() << "couldn't create folder for theme!";
qWarning() << "couldn't create folder for theme!"; m_palette = baseTheme->colorScheme();
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 (const Exception &e)
{
qWarning() << "Couldn't load css:" << e.cause() << "from" << cssFilePath;
m_styleSheet = baseTheme->appStyleSheet(); m_styleSheet = baseTheme->appStyleSheet();
return;
} }
}
else auto themeFilePath = FS::PathCombine(path, themeFile);
{
qDebug() << "No theme css present."; bool jsonDataIncomplete = false;
m_styleSheet = baseTheme->appStyleSheet();
try m_palette = baseTheme->colorScheme();
{ if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath, jsonDataIncomplete)) {
FS::write(cssFilePath, m_styleSheet.toUtf8()); themeDebugLog() << "Did not read theme json file correctly, writing new one to: " << themeFilePath;
m_name = "Custom";
m_palette = baseTheme->colorScheme();
m_fadeColor = baseTheme->fadeColor();
m_fadeAmount = baseTheme->fadeAmount();
m_widgets = baseTheme->qtTheme();
m_qssFilePath = "themeStyle.css";
} else {
m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor);
} }
catch (const Exception &e)
{ if (jsonDataIncomplete) {
qWarning() << "Couldn't write css:" << e.cause() << "to" << cssFilePath; writeThemeJson(fileInfo.absoluteFilePath(), m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath);
}
auto qssFilePath = FS::PathCombine(path, m_qssFilePath);
QFileInfo info(qssFilePath);
if (info.isFile()) {
try {
// TODO: validate css?
m_styleSheet = QString::fromUtf8(FS::read(qssFilePath));
} catch (const Exception& e) {
themeWarningLog() << "Couldn't load css:" << e.cause() << "from" << qssFilePath;
m_styleSheet = baseTheme->appStyleSheet();
}
} else {
themeDebugLog() << "No theme css present.";
m_styleSheet = baseTheme->appStyleSheet();
try {
FS::write(qssFilePath, m_styleSheet.toUtf8());
} catch (const Exception& e) {
themeWarningLog() << "Couldn't write css:" << e.cause() << "to" << qssFilePath;
}
}
} else {
m_id = fileInfo.fileName();
m_name = fileInfo.baseName();
QString path = fileInfo.filePath();
// themeDebugLog << "Theme ID: " << m_id;
// themeDebugLog << "Theme Name: " << m_name;
// themeDebugLog << "Theme Path: " << path;
if (!FS::ensureFilePathExists(path)) {
themeWarningLog() << m_name << " Theme file path doesn't exist!";
m_palette = baseTheme->colorScheme();
m_styleSheet = baseTheme->appStyleSheet();
return;
}
m_palette = baseTheme->colorScheme();
try {
// TODO: validate qss?
m_styleSheet = QString::fromUtf8(FS::read(path));
} catch (const Exception& e) {
themeWarningLog() << "Couldn't load qss:" << e.cause() << "from" << path;
m_styleSheet = baseTheme->appStyleSheet();
} }
} }
} }
@ -197,7 +243,6 @@ QStringList CustomTheme::searchPaths()
return { FS::PathCombine("themes", m_id, "resources") }; return { FS::PathCombine("themes", m_id, "resources") };
} }
QString CustomTheme::id() QString CustomTheme::id()
{ {
return m_id; return m_id;

View File

@ -1,11 +1,45 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Tayou <tayou@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QFileInfo>
#include "ITheme.h" #include "ITheme.h"
class CustomTheme: public ITheme class CustomTheme : public ITheme {
{ public:
public: CustomTheme(ITheme* baseTheme, QFileInfo& file, bool isManifest);
CustomTheme(ITheme * baseTheme, QString folder);
virtual ~CustomTheme() {} virtual ~CustomTheme() {}
QString id() override; QString id() override;
@ -19,7 +53,7 @@ public:
QString qtTheme() override; QString qtTheme() override;
QStringList searchPaths() override; QStringList searchPaths() override;
private: /* data */ private: /* data */
QPalette m_palette; QPalette m_palette;
QColor m_fadeColor; QColor m_fadeColor;
double m_fadeAmount; double m_fadeAmount;
@ -27,5 +61,5 @@ private: /* data */
QString m_name; QString m_name;
QString m_id; QString m_id;
QString m_widgets; QString m_widgets;
QString m_qssFilePath;
}; };

View File

@ -1,30 +1,65 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Tayou <tayou@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SystemTheme.h" #include "SystemTheme.h"
#include <QApplication> #include <QApplication>
#include <QStyle> #include <QStyle>
#include <QStyleFactory> #include <QStyleFactory>
#include <QDebug> #include <QDebug>
#include "ThemeManager.h"
SystemTheme::SystemTheme() SystemTheme::SystemTheme()
{ {
qDebug() << "Determining System Theme..."; themeDebugLog() << "Determining System Theme...";
const auto & style = QApplication::style(); const auto & style = QApplication::style();
systemPalette = style->standardPalette(); systemPalette = style->standardPalette();
QString lowerThemeName = style->objectName(); QString lowerThemeName = style->objectName();
qDebug() << "System theme seems to be:" << lowerThemeName; themeDebugLog() << "System theme seems to be:" << lowerThemeName;
QStringList styles = QStyleFactory::keys(); QStringList styles = QStyleFactory::keys();
for(auto &st: styles) for(auto &st: styles)
{ {
qDebug() << "Considering theme from theme factory:" << st.toLower(); themeDebugLog() << "Considering theme from theme factory:" << st.toLower();
if(st.toLower() == lowerThemeName) if(st.toLower() == lowerThemeName)
{ {
systemTheme = st; systemTheme = st;
qDebug() << "System theme has been determined to be:" << systemTheme; themeDebugLog() << "System theme has been determined to be:" << systemTheme;
return; return;
} }
} }
// fall back to fusion if we can't find the current theme. // fall back to fusion if we can't find the current theme.
systemTheme = "Fusion"; systemTheme = "Fusion";
qDebug() << "System theme not found, defaulted to Fusion"; themeDebugLog() << "System theme not found, defaulted to Fusion";
} }
void SystemTheme::apply(bool initial) void SystemTheme::apply(bool initial)

View File

@ -0,0 +1,155 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Tayou <tayou@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ThemeManager.h"
#include <QApplication>
#include <QDir>
#include <QDirIterator>
#include <QIcon>
#include "ui/themes/BrightTheme.h"
#include "ui/themes/CustomTheme.h"
#include "ui/themes/DarkTheme.h"
#include "ui/themes/SystemTheme.h"
#include "Application.h"
#ifdef Q_OS_WIN
#include <windows.h>
// this is needed for versionhelpers.h, it is also included in WinDarkmode, but we can't rely on that.
// Ultimately this should be included in versionhelpers, but that is outside of the project.
#include "ui/WinDarkmode.h"
#include <versionhelpers.h>
#endif
ThemeManager::ThemeManager(MainWindow* mainWindow)
{
m_mainWindow = mainWindow;
InitializeThemes();
}
/// @brief Adds the Theme to the list of themes
/// @param theme The Theme to add
/// @return Theme ID
QString ThemeManager::AddTheme(std::unique_ptr<ITheme> theme)
{
QString id = theme->id();
m_themes.emplace(id, std::move(theme));
return id;
}
/// @brief Gets the Theme from the List via ID
/// @param themeId Theme ID of theme to fetch
/// @return Theme at themeId
ITheme* ThemeManager::GetTheme(QString themeId)
{
return m_themes[themeId].get();
}
void ThemeManager::InitializeThemes()
{
// Icon themes
{
// TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies!
// set icon theme search path!
auto searchPaths = QIcon::themeSearchPaths();
searchPaths.append("iconthemes");
QIcon::setThemeSearchPaths(searchPaths);
themeDebugLog() << "<> Icon themes initialized.";
}
// Initialize widget themes
{
themeDebugLog() << "<> Initializing Widget Themes";
themeDebugLog() << "Loading Built-in Theme:" << AddTheme(std::make_unique<SystemTheme>());
auto darkThemeId = AddTheme(std::make_unique<DarkTheme>());
themeDebugLog() << "Loading Built-in Theme:" << darkThemeId;
themeDebugLog() << "Loading Built-in Theme:" << AddTheme(std::make_unique<BrightTheme>());
// TODO: need some way to differentiate same name themes in different subdirectories (maybe smaller grey text next to theme name in
// dropdown?)
QString themeFolder = QDir("./themes/").absoluteFilePath("");
themeDebugLog() << "Theme Folder Path: " << themeFolder;
QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (directoryIterator.hasNext()) {
QDir dir(directoryIterator.next());
QFileInfo themeJson(dir.absoluteFilePath("theme.json"));
if (themeJson.exists()) {
// Load "theme.json" based themes
themeDebugLog() << "Loading JSON Theme from:" << themeJson.absoluteFilePath();
AddTheme(std::make_unique<CustomTheme>(GetTheme(darkThemeId), themeJson, true));
} else {
// Load pure QSS Themes
QDirIterator stylesheetFileIterator(dir.absoluteFilePath(""), { "*.qss", "*.css" }, QDir::Files);
while (stylesheetFileIterator.hasNext()) {
QFile customThemeFile(stylesheetFileIterator.next());
QFileInfo customThemeFileInfo(customThemeFile);
themeDebugLog() << "Loading QSS Theme from:" << customThemeFileInfo.absoluteFilePath();
AddTheme(std::make_unique<CustomTheme>(GetTheme(darkThemeId), customThemeFileInfo, false));
}
}
}
themeDebugLog() << "<> Widget themes initialized.";
}
}
QList<ITheme*> ThemeManager::getValidApplicationThemes()
{
QList<ITheme*> ret;
ret.reserve(m_themes.size());
for (auto&& [id, theme] : m_themes) {
ret.append(theme.get());
}
return ret;
}
void ThemeManager::setIconTheme(const QString& name)
{
QIcon::setThemeName(name);
}
void ThemeManager::applyCurrentlySelectedTheme()
{
setIconTheme(APPLICATION->settings()->get("IconTheme").toString());
themeDebugLog() << "<> Icon theme set.";
setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString(), true);
themeDebugLog() << "<> Application theme set.";
}
void ThemeManager::setApplicationTheme(const QString& name, bool initial)
{
auto systemPalette = qApp->palette();
auto themeIter = m_themes.find(name);
if (themeIter != m_themes.end()) {
auto& theme = themeIter->second;
themeDebugLog() << "applying theme" << theme->name();
theme->apply(initial);
#ifdef Q_OS_WIN
if (m_mainWindow && IsWindows10OrGreater()) {
if (QString::compare(theme->id(), "dark") == 0) {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
} else {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
}
}
#endif
} else {
themeWarningLog() << "Tried to set invalid theme:" << name;
}
}

View File

@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Tayou <tayou@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include "ui/MainWindow.h"
#include "ui/themes/ITheme.h"
inline auto themeDebugLog()
{
return qDebug() << "[Theme]";
}
inline auto themeWarningLog()
{
return qWarning() << "[Theme]";
}
class ThemeManager {
public:
ThemeManager(MainWindow* mainWindow);
// maybe make private? Or put in ctor?
void InitializeThemes();
QList<ITheme*> getValidApplicationThemes();
void setIconTheme(const QString& name);
void applyCurrentlySelectedTheme();
void setApplicationTheme(const QString& name, bool initial);
private:
std::map<QString, std::unique_ptr<ITheme>> m_themes;
MainWindow* m_mainWindow;
QString AddTheme(std::unique_ptr<ITheme> theme);
ITheme* GetTheme(QString themeId);
};