diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:45:07 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:45:07 +0300 |
| commit | 31b9a8949ed0a288143e23bf739f2eb64fdc63be (patch) | |
| tree | 8a984fa143c38fccad461a77792d6864f3e82cd3 /meshmc/launcher/ui/themes/ThemeManager.cpp | |
| parent | 934382c8a1ce738589dee9ee0f14e1cec812770e (diff) | |
| parent | fad6a1066616b69d7f5fef01178efdf014c59537 (diff) | |
| download | Project-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.tar.gz Project-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.zip | |
Add 'meshmc/' from commit 'fad6a1066616b69d7f5fef01178efdf014c59537'
git-subtree-dir: meshmc
git-subtree-mainline: 934382c8a1ce738589dee9ee0f14e1cec812770e
git-subtree-split: fad6a1066616b69d7f5fef01178efdf014c59537
Diffstat (limited to 'meshmc/launcher/ui/themes/ThemeManager.cpp')
| -rw-r--r-- | meshmc/launcher/ui/themes/ThemeManager.cpp | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/meshmc/launcher/ui/themes/ThemeManager.cpp b/meshmc/launcher/ui/themes/ThemeManager.cpp new file mode 100644 index 0000000000..73d0ee9330 --- /dev/null +++ b/meshmc/launcher/ui/themes/ThemeManager.cpp @@ -0,0 +1,369 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 "ITheme.h" +#include "SystemTheme.h" +#include "DarkTheme.h" +#include "BrightTheme.h" +#include "CustomTheme.h" +#include "CatPack.h" + +#include "Application.h" +#include "Exception.h" +#include <QApplication> +#include <QDebug> +#include <QDirIterator> +#include <QImageReader> +#include <QSet> +#include <QStyle> +#include <QStyleFactory> +#include <QSysInfo> +#include <xdgicon.h> + +ThemeManager::ThemeManager() +{ + const auto& style = QApplication::style(); + m_defaultStyle = style->objectName(); + m_defaultPalette = QApplication::palette(); + + // Default "System" theme + addTheme( + std::make_unique<SystemTheme>(m_defaultStyle, m_defaultPalette, true)); + + // Built-in Fusion themes + auto darkTheme = new DarkTheme(); + addTheme(std::unique_ptr<ITheme>(darkTheme)); + addTheme(std::make_unique<BrightTheme>()); + + // System widget themes from QStyleFactory + QStringList styles = QStyleFactory::keys(); + for (auto& st : styles) { +#ifdef Q_OS_WINDOWS + if (QSysInfo::productVersion() != "11" && st == "windows11") { + continue; + } +#endif + addTheme(std::make_unique<SystemTheme>(st, m_defaultPalette, false)); + } + + // Custom theme + addTheme(std::make_unique<CustomTheme>(darkTheme, "custom")); + + initIconThemes(); + initializeCatPacks(); +} + +void ThemeManager::addTheme(std::unique_ptr<ITheme> theme) +{ + QString id = theme->id(); + m_themes.insert(std::make_pair(id, std::move(theme))); +} + +ITheme* ThemeManager::getTheme(const QString& id) +{ + auto it = m_themes.find(id); + if (it != m_themes.end()) { + return it->second.get(); + } + return nullptr; +} + +void ThemeManager::setApplicationTheme(const QString& id, bool initial) +{ + auto theme = getTheme(id); + if (theme) { + theme->apply(initial); + } else { + qWarning() << "Tried to set invalid theme:" << id; + } +} + +void ThemeManager::setIconTheme(const QString& name) +{ + XdgIcon::setThemeName(name); +} + +void ThemeManager::applyCurrentlySelectedTheme(bool initial) +{ + auto settings = APPLICATION->settings(); + + // Apply widget theme first (sets palette) + auto applicationTheme = settings->get("ApplicationTheme").toString(); + if (applicationTheme.isEmpty()) { + applicationTheme = "system"; + } + setApplicationTheme(applicationTheme, initial); + + // Auto-resolve icon variant based on the now-active palette brightness + auto iconTheme = settings->get("IconTheme").toString(); + if (!iconTheme.isEmpty()) { + auto resolved = bestIconThemeForPalette(iconTheme); + if (resolved != iconTheme) { + settings->set("IconTheme", resolved); + } + setIconTheme(resolved); + } +} + +std::vector<ITheme*> ThemeManager::allThemes() +{ + std::vector<ITheme*> ret; + for (auto& pair : m_themes) { + ret.push_back(pair.second.get()); + } + return ret; +} + +QStringList ThemeManager::families() +{ + QStringList ret; + QSet<QString> seen; + for (auto& pair : m_themes) { + QString fam = pair.second->family(); + if (!seen.contains(fam)) { + seen.insert(fam); + ret.append(fam); + } + } + return ret; +} + +std::vector<ITheme*> ThemeManager::themesInFamily(const QString& family) +{ + std::vector<ITheme*> ret; + for (auto& pair : m_themes) { + if (pair.second->family() == family) { + ret.push_back(pair.second.get()); + } + } + return ret; +} + +QList<IconThemeEntry> ThemeManager::iconThemes() const +{ + return m_iconThemes; +} + +QStringList ThemeManager::iconThemeFamilies() const +{ + QStringList ret; + QSet<QString> seen; + for (const auto& entry : m_iconThemes) { + const QString& fam = entry.family; + if (!seen.contains(fam)) { + seen.insert(fam); + ret.append(fam); + } + } + return ret; +} + +QList<IconThemeEntry> +ThemeManager::iconThemesInFamily(const QString& family) const +{ + QList<IconThemeEntry> ret; + for (const auto& entry : m_iconThemes) { + if (entry.family == family) { + ret.append(entry); + } + } + return ret; +} + +QString ThemeManager::resolveIconTheme(const QString& family) const +{ + auto entries = iconThemesInFamily(family); + if (entries.size() <= 1) { + return entries.isEmpty() ? QString() : entries[0].id; + } + + // Check if family has variants + bool hasVariants = false; + for (const auto& entry : entries) { + if (!entry.variant.isEmpty()) { + hasVariants = true; + break; + } + } + + if (!hasVariants) { + return entries[0].id; + } + + // Auto-detect based on current palette brightness + auto windowColor = QApplication::palette().color(QPalette::Window); + bool isDark = windowColor.lightnessF() < 0.5; + + for (const auto& entry : entries) { + QString v = entry.variant.toLower(); + if (isDark && v == "dark") + return entry.id; + if (!isDark && v == "light") + return entry.id; + } + + return entries[0].id; +} + +QString +ThemeManager::bestIconThemeForPalette(const QString& currentIconId) const +{ + // Find the family of the current icon theme + QString family; + for (const auto& entry : m_iconThemes) { + if (entry.id == currentIconId) { + family = entry.family; + break; + } + } + + if (family.isEmpty()) { + return currentIconId; + } + + // Resolve the best variant for that family based on current palette + QString resolved = resolveIconTheme(family); + return resolved.isEmpty() ? currentIconId : resolved; +} + +void ThemeManager::initIconThemes() +{ + m_iconThemes = { + {"pe_colored", QObject::tr("Default"), QObject::tr("Default"), + QString()}, + {"multimc", QStringLiteral("MultiMC"), QStringLiteral("MultiMC"), + QString()}, + {"pe_dark", QObject::tr("Simple (Dark Icons)"), + QObject::tr("Simple (Dark Icons)"), QString()}, + {"pe_light", QObject::tr("Simple (Light Icons)"), + QObject::tr("Simple (Light Icons)"), QString()}, + {"pe_blue", QObject::tr("Simple (Blue Icons)"), + QObject::tr("Simple (Blue Icons)"), QString()}, + {"pe_colored", QObject::tr("Simple (Colored Icons)"), + QObject::tr("Simple (Colored Icons)"), QString()}, + {"OSX", QStringLiteral("OSX"), QStringLiteral("OSX"), QString()}, + {"iOS", QStringLiteral("iOS"), QStringLiteral("iOS"), QString()}, + {"flat", QStringLiteral("Flat Light"), QStringLiteral("Flat"), + QObject::tr("Light")}, + {"flat_white", QStringLiteral("Flat Dark"), QStringLiteral("Flat"), + QObject::tr("Dark")}, + {"breeze_dark", QStringLiteral("Breeze Dark"), QStringLiteral("Breeze"), + QObject::tr("Dark")}, + {"breeze_light", QStringLiteral("Breeze Light"), + QStringLiteral("Breeze"), QObject::tr("Light")}, + {"custom", QObject::tr("Custom"), QObject::tr("Custom"), QString()}, + }; +} + +void ThemeManager::initializeCatPacks() +{ + QList<std::pair<QString, QString>> defaultCats{ + {"kitteh", QObject::tr("Background Cat (from MultiMC)")}, + {"rory", QObject::tr("Rory ID 11 (drawn by Ashtaka)")}, + {"rory-flat", + QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)")}, + {"teawie", QObject::tr("Teawie (drawn by SympathyTea)")}}; + + for (const auto& [id, name] : defaultCats) { + addCatPack(std::make_unique<BasicCatPack>(id, name)); + } + + // Create catpacks folder in data directory + m_catPacksFolder = QDir("catpacks"); + if (!m_catPacksFolder.mkpath(".")) + qWarning() << "Couldn't create catpacks folder"; + + QStringList supportedImageFormats; + for (const auto& format : QImageReader::supportedImageFormats()) { + supportedImageFormats.append("*." + format); + } + + auto loadFiles = [this, &supportedImageFormats](const QDir& dir) { + QDirIterator it(dir.absolutePath(), supportedImageFormats, QDir::Files); + while (it.hasNext()) { + QFileInfo info(it.next()); + addCatPack(std::make_unique<FileCatPack>(info)); + } + }; + + // Load image files in catpacks folder root + loadFiles(m_catPacksFolder); + + // Load subdirectories + QDirIterator directoryIterator(m_catPacksFolder.path(), + QDir::Dirs | QDir::NoDotAndDotDot); + while (directoryIterator.hasNext()) { + QDir dir(directoryIterator.next()); + QFileInfo manifest(dir.absoluteFilePath("catpack.json")); + + if (manifest.isFile()) { + try { + addCatPack(std::make_unique<JsonCatPack>(manifest)); + } catch (const Exception& e) { + qWarning() << "Couldn't load catpack json:" << e.cause(); + } + } else { + loadFiles(dir); + } + } +} + +void ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack) +{ + QString id = catPack->id(); + if (m_catPacks.find(id) == m_catPacks.end()) + m_catPacks.emplace(id, std::move(catPack)); + else + qWarning() << "CatPack" << id << "not added to prevent id duplication"; +} + +QString ThemeManager::getCatPack(const QString& catName) +{ + QString id = catName.isEmpty() + ? APPLICATION->settings()->get("BackgroundCat").toString() + : catName; + + auto it = m_catPacks.find(id); + if (it != m_catPacks.end()) + return it->second->path(); + + // Fallback to first available + if (!m_catPacks.empty()) + return m_catPacks.begin()->second->path(); + + return QString(); +} + +QList<CatPack*> ThemeManager::getValidCatPacks() +{ + QList<CatPack*> ret; + ret.reserve(m_catPacks.size()); + for (auto& [id, pack] : m_catPacks) { + ret.append(pack.get()); + } + return ret; +} + +QDir ThemeManager::getCatPacksFolder() +{ + return m_catPacksFolder; +} |
