mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
[Theme] Add scheme-variant asset resolution (#7275)
* Add scheme-variant theme asset resolution (#7209-1) ThemeManager::assetPath() and schemeVariantPath() resolve a theme asset to its scheme-variant file (prefix-light/dark.png) with fallback to the plain asset, and themePixmap()/loadBrush()/loadExtraBrush() use them. CSS files load style-dark.css or style-light.css when present. Home widget re-resolves its background on theme change. Link pixel_map_generator.cpp into the oracle target, which needs Qt6::Xml for QDomDocument. * Fix clang-format wrap of theme format probe lists --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
202a5ac958
commit
e9bf1e6e46
7 changed files with 147 additions and 12 deletions
|
|
@ -28,7 +28,7 @@ if(WITH_CLIENT)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
if(WITH_ORACLE)
|
if(WITH_ORACLE)
|
||||||
set(_ORACLE_NEEDED Concurrent Network Svg Widgets)
|
set(_ORACLE_NEEDED Concurrent Network Svg Widgets Xml)
|
||||||
endif()
|
endif()
|
||||||
if(TEST)
|
if(TEST)
|
||||||
# Union of Qt modules required across all test targets (independent of application targets).
|
# Union of Qt modules required across all test targets (independent of application targets).
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include "pixel_map_generator.h"
|
#include "pixel_map_generator.h"
|
||||||
|
|
||||||
|
#include "theme_manager.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDomDocument>
|
#include <QDomDocument>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
@ -83,7 +85,13 @@ static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnl
|
||||||
/**
|
/**
|
||||||
* Try to load path image from non-SVG formats, otherwise fall back to SVG.
|
* Try to load path image from non-SVG formats, otherwise fall back to SVG.
|
||||||
* This is to allow custom themes to support non-SVG format type overrides, since SVG requires custom loading.
|
* This is to allow custom themes to support non-SVG format type overrides, since SVG requires custom loading.
|
||||||
* @param path The path to the file, with no file extension. File formats will be automatically detected.
|
*
|
||||||
|
* The path may already carry the resolved file extension (e.g. via
|
||||||
|
* ThemeManager::assetPath); such paths are loaded directly. Otherwise a
|
||||||
|
* format-agnostic lookup probes png, jpg and finally svg.
|
||||||
|
*
|
||||||
|
* @param path The path to the file, with no file extension unless the caller
|
||||||
|
* already resolved it. File formats will be automatically detected.
|
||||||
* @param size The desired size of the pixmap.
|
* @param size The desired size of the pixmap.
|
||||||
* @param expandOnly If true, then keep the size of the initial pixmap to at least the size (Only relevant if SVG).
|
* @param expandOnly If true, then keep the size of the initial pixmap to at least the size (Only relevant if SVG).
|
||||||
*
|
*
|
||||||
|
|
@ -91,6 +99,19 @@ static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnl
|
||||||
*/
|
*/
|
||||||
static QPixmap tryLoadImage(const QString &path, const QSize &size, bool expandOnly = false)
|
static QPixmap tryLoadImage(const QString &path, const QSize &size, bool expandOnly = false)
|
||||||
{
|
{
|
||||||
|
if (path.endsWith(QLatin1String(".svg"), Qt::CaseInsensitive)) {
|
||||||
|
return loadSvg(path, size, expandOnly);
|
||||||
|
}
|
||||||
|
if (path.endsWith(QLatin1String(".png"), Qt::CaseInsensitive) ||
|
||||||
|
path.endsWith(QLatin1String(".jpg"), Qt::CaseInsensitive) ||
|
||||||
|
path.endsWith(QLatin1String(".jpeg"), Qt::CaseInsensitive)) {
|
||||||
|
QPixmap pix(path);
|
||||||
|
if (!pix.isNull()) {
|
||||||
|
return pix.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const auto formats = {"png", "jpg"};
|
const auto formats = {"png", "jpg"};
|
||||||
|
|
||||||
QPixmap returnPixmap;
|
QPixmap returnPixmap;
|
||||||
|
|
@ -112,7 +133,8 @@ QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name)
|
||||||
return pmCache.value(key);
|
return pmCache.value(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap pixmap = tryLoadImage("theme:phases/" + name, QSize(height, height));
|
QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("phases/") + name),
|
||||||
|
QSize(height, height));
|
||||||
|
|
||||||
pmCache.insert(key, pixmap);
|
pmCache.insert(key, pixmap);
|
||||||
return pixmap;
|
return pixmap;
|
||||||
|
|
@ -399,7 +421,8 @@ QPixmap LockPixmapGenerator::generatePixmap(int height)
|
||||||
return pmCache.value(key);
|
return pmCache.value(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap pixmap = tryLoadImage("theme:icons/lock", QSize(height, height), true);
|
QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/lock")),
|
||||||
|
QSize(height, height), true);
|
||||||
pmCache.insert(key, pixmap);
|
pmCache.insert(key, pixmap);
|
||||||
return pixmap;
|
return pixmap;
|
||||||
}
|
}
|
||||||
|
|
@ -414,7 +437,8 @@ QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed";
|
QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed";
|
||||||
QPixmap pixmap = tryLoadImage("theme:icons/" + name, QSize(height, height), true);
|
QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/") + name),
|
||||||
|
QSize(height, height), true);
|
||||||
|
|
||||||
pmCache.insert(key, pixmap);
|
pmCache.insert(key, pixmap);
|
||||||
return pixmap;
|
return pixmap;
|
||||||
|
|
@ -475,6 +499,13 @@ QHash<QString, QPixmap> ManaSymbolPixmapGenerator::scaledCache;
|
||||||
|
|
||||||
QPixmap loadColorAdjustedPixmap(const QString &name)
|
QPixmap loadColorAdjustedPixmap(const QString &name)
|
||||||
{
|
{
|
||||||
|
// Prefer an authored scheme-qualified variant when one exists for this asset.
|
||||||
|
const QString variant = themeManager->schemeVariantPath(QStringView(name).mid(QStringLiteral("theme:").size()));
|
||||||
|
if (!variant.isEmpty()) {
|
||||||
|
return QPixmap(QStringLiteral("theme:") + variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy fallback: runtime-invert for dark mode when no authored variant.
|
||||||
if (qApp->palette().windowText().color().lightness() > 200) {
|
if (qApp->palette().windowText().color().lightness() > 200) {
|
||||||
QImage img(name);
|
QImage img(name);
|
||||||
img.invertPixels();
|
img.invertPixels();
|
||||||
|
|
@ -485,3 +516,21 @@ QPixmap loadColorAdjustedPixmap(const QString &name)
|
||||||
return QPixmap(name);
|
return QPixmap(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPixmap themePixmap(QStringView prefix)
|
||||||
|
{
|
||||||
|
const QString resolved = themeManager->assetPath(prefix);
|
||||||
|
return QPixmap(QStringLiteral("theme:") + resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearPixmapGeneratorCaches()
|
||||||
|
{
|
||||||
|
PhasePixmapGenerator::clear();
|
||||||
|
CounterPixmapGenerator::clear();
|
||||||
|
PingPixmapGenerator::clear();
|
||||||
|
CountryPixmapGenerator::clear();
|
||||||
|
UserLevelPixmapGenerator::clear();
|
||||||
|
LockPixmapGenerator::clear();
|
||||||
|
DropdownIconPixmapGenerator::clear();
|
||||||
|
ManaSymbolPixmapGenerator::clear();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,4 +156,15 @@ public:
|
||||||
|
|
||||||
QPixmap loadColorAdjustedPixmap(const QString &name);
|
QPixmap loadColorAdjustedPixmap(const QString &name);
|
||||||
|
|
||||||
|
// Loads a "theme:" asset (with no file extension in prefix), preferring the
|
||||||
|
// scheme-qualified variant (prefix-dark / prefix-light, resolved via
|
||||||
|
// ThemeManager::assetPath) and falling back to the plain asset. Callers load
|
||||||
|
// the returned path directly. Use for scheme-sensitive pixmaps like
|
||||||
|
// backgrounds, the card back, and the app logo.
|
||||||
|
QPixmap themePixmap(QStringView prefix);
|
||||||
|
|
||||||
|
// Clears every PixmapGenerator's static cache so scheme variants are
|
||||||
|
// re-resolved when the active theme or color scheme changes.
|
||||||
|
void clearPixmapGeneratorCaches();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
#include "theme_manager.h"
|
#include "theme_manager.h"
|
||||||
|
|
||||||
#include "../../client/settings/cache_settings.h"
|
#include "../../client/settings/cache_settings.h"
|
||||||
|
#include "pixel_map_generator.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QLibraryInfo>
|
#include <QLibraryInfo>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QMetaEnum>
|
#include <QMetaEnum>
|
||||||
|
|
@ -140,6 +142,48 @@ bool ThemeManager::isDarkMode(const QString &themeDirPath) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ThemeManager::schemeVariantPath(QStringView prefix) const
|
||||||
|
{
|
||||||
|
static const QStringList formats = {QStringLiteral(".png"), QStringLiteral(".jpg"), QStringLiteral(".jpeg"),
|
||||||
|
QStringLiteral(".svg")};
|
||||||
|
const QString scheme = isDarkMode(currentThemePath) ? QStringLiteral("dark") : QStringLiteral("light");
|
||||||
|
const QString variantStem = prefix.toString() + QLatin1Char('-') + scheme;
|
||||||
|
|
||||||
|
for (const QString &format : formats) {
|
||||||
|
if (QFileInfo::exists(QStringLiteral("theme:") + variantStem + format)) {
|
||||||
|
return variantStem + format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ThemeManager::assetPath(QStringView prefix) const
|
||||||
|
{
|
||||||
|
// Probe order mirrors tryLoadImage: a theme may override the default SVG
|
||||||
|
// with a raster of the same stem, so raster wins over SVG within a stem.
|
||||||
|
static const QStringList formats = {QStringLiteral(".png"), QStringLiteral(".jpg"), QStringLiteral(".jpeg"),
|
||||||
|
QStringLiteral(".svg")};
|
||||||
|
|
||||||
|
auto findExisting = [](const QString &stem) {
|
||||||
|
for (const QString &format : formats) {
|
||||||
|
if (QFileInfo::exists(QStringLiteral("theme:") + stem + format)) {
|
||||||
|
return stem + format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prefer the scheme-qualified variant when it exists, else the plain
|
||||||
|
// asset as the super fallback. Both return the resolved path including
|
||||||
|
// its file extension so callers can load it directly.
|
||||||
|
const QString variant = schemeVariantPath(prefix);
|
||||||
|
if (!variant.isEmpty()) {
|
||||||
|
return variant;
|
||||||
|
}
|
||||||
|
const QString resolvedPlain = findExisting(prefix.toString());
|
||||||
|
return resolvedPlain.isEmpty() ? prefix.toString() : resolvedPlain;
|
||||||
|
}
|
||||||
|
|
||||||
bool ThemeManager::isBuiltInTheme()
|
bool ThemeManager::isBuiltInTheme()
|
||||||
{
|
{
|
||||||
const auto themeName = SettingsCache::instance().getThemeName();
|
const auto themeName = SettingsCache::instance().getThemeName();
|
||||||
|
|
@ -195,7 +239,7 @@ QStringMap &ThemeManager::getAvailableThemes()
|
||||||
QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor)
|
QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor)
|
||||||
{
|
{
|
||||||
QBrush brush;
|
QBrush brush;
|
||||||
QPixmap tmp = QPixmap("theme:zones/" + fileName);
|
QPixmap tmp = QPixmap("theme:" + assetPath(QStringLiteral("zones/") + fileName));
|
||||||
if (tmp.isNull()) {
|
if (tmp.isNull()) {
|
||||||
brush.setColor(fallbackColor);
|
brush.setColor(fallbackColor);
|
||||||
brush.setStyle(Qt::SolidPattern);
|
brush.setStyle(Qt::SolidPattern);
|
||||||
|
|
@ -209,7 +253,7 @@ QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor)
|
||||||
QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush)
|
QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush)
|
||||||
{
|
{
|
||||||
QBrush brush;
|
QBrush brush;
|
||||||
QPixmap tmp = QPixmap("theme:zones/" + fileName);
|
QPixmap tmp = QPixmap("theme:" + assetPath(QStringLiteral("zones/") + fileName));
|
||||||
|
|
||||||
if (tmp.isNull()) {
|
if (tmp.isNull()) {
|
||||||
brush = fallbackBrush;
|
brush = fallbackBrush;
|
||||||
|
|
@ -393,12 +437,22 @@ void ThemeManager::themeChangedSlot()
|
||||||
currentThemePath = dirPath;
|
currentThemePath = dirPath;
|
||||||
QDir dir(dirPath);
|
QDir dir(dirPath);
|
||||||
|
|
||||||
// CSS
|
// CSS — prefer the scheme-qualified stylesheet (style-dark.css /
|
||||||
if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) {
|
// style-light.css) when present, else the plain style.css as fallback.
|
||||||
|
if (!dirPath.isEmpty()) {
|
||||||
|
const QString scheme = isDarkMode(dirPath) ? QStringLiteral("dark") : QStringLiteral("light");
|
||||||
|
const QString schemeCss = QFileInfo(QStringLiteral(STYLE_CSS_NAME)).completeBaseName() + QLatin1Char('-') +
|
||||||
|
scheme + QStringLiteral(".css");
|
||||||
|
if (dir.exists(schemeCss)) {
|
||||||
|
qApp->setStyleSheet("file:///" + dir.absoluteFilePath(schemeCss));
|
||||||
|
} else if (dir.exists(STYLE_CSS_NAME)) {
|
||||||
qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
|
qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
|
||||||
} else {
|
} else {
|
||||||
qApp->setStyleSheet("");
|
qApp->setStyleSheet("");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
qApp->setStyleSheet("");
|
||||||
|
}
|
||||||
|
|
||||||
// load theme.cfg for style + scheme preference
|
// load theme.cfg for style + scheme preference
|
||||||
ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
||||||
|
|
@ -446,6 +500,7 @@ void ThemeManager::themeChangedSlot()
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmapCache::clear();
|
QPixmapCache::clear();
|
||||||
|
clearPixmapGeneratorCaches();
|
||||||
|
|
||||||
emit themeChanged();
|
emit themeChanged();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,20 @@ public:
|
||||||
// Load/save per-scheme palette colors
|
// Load/save per-scheme palette colors
|
||||||
static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme);
|
static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme);
|
||||||
static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg);
|
static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg);
|
||||||
|
// Resolve prefix to a scheme-qualified "theme:" path. Existence is probed
|
||||||
|
// internally across the formats themes may ship (.png/.jpg/.svg), so
|
||||||
|
// callers load the returned path directly. Prefers "<prefix>-<dark|light>"
|
||||||
|
// when a file exists at that stem, otherwise the plain "<prefix>" as the
|
||||||
|
// super fallback. The resolved scheme covers explicit light/dark as well
|
||||||
|
// as OS-resolved "system". Returns the path with its file extension when a
|
||||||
|
// match is found; unqualified assets keep working unchanged.
|
||||||
|
QString assetPath(QStringView prefix) const;
|
||||||
|
// Like assetPath, but resolves only the scheme-qualified variant
|
||||||
|
// ("<prefix>-<dark|light>.<ext>") and returns an empty string when no
|
||||||
|
// variant exists — it never falls back to the plain "<prefix>" asset.
|
||||||
|
// Callers that must distinguish "no authored variant" (e.g. to keep a
|
||||||
|
// legacy runtime fallback alive) should use this instead of assetPath.
|
||||||
|
QString schemeVariantPath(QStringView prefix) const;
|
||||||
// Load the theme's shipped default palette, falling back to the system
|
// Load the theme's shipped default palette, falling back to the system
|
||||||
// theme directory when it is absent from the resolved (user) directory.
|
// theme directory when it is absent from the resolved (user) directory.
|
||||||
static PaletteConfig
|
static PaletteConfig
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "../../../client/settings/cache_settings.h"
|
#include "../../../client/settings/cache_settings.h"
|
||||||
#include "../../../interface/widgets/tabs/tab_supervisor.h"
|
#include "../../../interface/widgets/tabs/tab_supervisor.h"
|
||||||
|
#include "../../pixel_map_generator.h"
|
||||||
#include "../../theme_manager.h"
|
#include "../../theme_manager.h"
|
||||||
#include "../../window_main.h"
|
#include "../../window_main.h"
|
||||||
#include "../cards/art_crop_attribution.h"
|
#include "../cards/art_crop_attribution.h"
|
||||||
|
|
@ -20,7 +21,8 @@
|
||||||
#include <libcockatrice/settings/paths_settings.h>
|
#include <libcockatrice/settings/paths_settings.h>
|
||||||
|
|
||||||
HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
||||||
: QWidget(parent), tabSupervisor(_tabSupervisor), background("theme:backgrounds/home"), overlay("theme:cockatrice")
|
: QWidget(parent), tabSupervisor(_tabSupervisor), background(themePixmap(QStringLiteral("backgrounds/home"))),
|
||||||
|
overlay(themePixmap(QStringLiteral("cockatrice")))
|
||||||
{
|
{
|
||||||
layout = new QGridLayout(this);
|
layout = new QGridLayout(this);
|
||||||
|
|
||||||
|
|
@ -56,6 +58,9 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
|
||||||
&HomeWidget::initializeBackgroundFromSource);
|
&HomeWidget::initializeBackgroundFromSource);
|
||||||
connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this,
|
connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this,
|
||||||
&HomeWidget::updateButtonsToBackgroundColor);
|
&HomeWidget::updateButtonsToBackgroundColor);
|
||||||
|
// Scheme flips (light/dark/system with an OS switch) fire on themeManager,
|
||||||
|
// not on SettingsCache::themeChanged, so re-resolve the variant background.
|
||||||
|
connect(themeManager, &ThemeManager::themeChanged, this, &HomeWidget::initializeBackgroundFromSource);
|
||||||
connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabButtonColorChanged, this,
|
connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabButtonColorChanged, this,
|
||||||
&HomeWidget::updateButtonsToBackgroundColor);
|
&HomeWidget::updateButtonsToBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +79,7 @@ void HomeWidget::initializeBackgroundFromSource()
|
||||||
switch (backgroundSourceType) {
|
switch (backgroundSourceType) {
|
||||||
case BackgroundSources::Theme:
|
case BackgroundSources::Theme:
|
||||||
cardChangeTimer->stop();
|
cardChangeTimer->stop();
|
||||||
background = QPixmap("theme:backgrounds/home");
|
background = themePixmap(QStringLiteral("backgrounds/home"));
|
||||||
backgroundSourceDeck = DeckList();
|
backgroundSourceDeck = DeckList();
|
||||||
backgroundSourceCard->setCard(ExactCard());
|
backgroundSourceCard->setCard(ExactCard());
|
||||||
updateButtonsToBackgroundColor();
|
updateButtonsToBackgroundColor();
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ set(oracle_SOURCES
|
||||||
../cockatrice/src/client/settings/card_counter_settings.cpp
|
../cockatrice/src/client/settings/card_counter_settings.cpp
|
||||||
../cockatrice/src/client/settings/shortcuts_settings.cpp
|
../cockatrice/src/client/settings/shortcuts_settings.cpp
|
||||||
../cockatrice/src/client/network/update/client/release_channel.cpp
|
../cockatrice/src/client/network/update/client/release_channel.cpp
|
||||||
|
../cockatrice/src/interface/pixel_map_generator.cpp
|
||||||
../cockatrice/src/interface/theme_config.cpp
|
../cockatrice/src/interface/theme_config.cpp
|
||||||
../cockatrice/src/interface/theme_manager.cpp
|
../cockatrice/src/interface/theme_manager.cpp
|
||||||
../cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp
|
../cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue