Compare commits

...

14 commits

Author SHA1 Message Date
Lukas Brübach
803fa21753 Update light fabric background 2026-09-12 20:17:02 +02:00
Lukas Brübach
f7d7d6bbdd Update light plasma background 2026-09-12 20:17:02 +02:00
Lukas Brübach
85d96730e6 Update velvet marble backgrounds 2026-09-12 20:17:02 +02:00
Lukas Brübach
425e14c2dd Update leather backgrounds 2026-09-12 20:17:02 +02:00
Lukas Brübach
305df2d724 [Home] Add option to disable the home tab background dim
Adds a 'Dim the home tab background' checkbox to the Home tab settings
page (Appearance). When unchecked, HomeWidget skips the translucent
black overlay it paints over the whole background. Default is on,
preserving current behavior; the home tab repaints live on change.
2026-09-12 20:17:02 +02:00
Lukas Brübach
d8e488c713 [Themes] Align light plasma home background with the dark variant
Recolored the Plasma light home background to mirror the dark one:
brighter violet/cyan blooms, vivid azure spark arcs, and higher accent
saturation against the same pale-lavender key. Brightness is unchanged
so it still reads as a light scheme.
2026-09-12 20:17:02 +02:00
Lukas Brübach
7501932e25 [Themes] Ship Fusion style + scheme backgrounds for the image themes
Address PR review:
- Add theme.cfg ([Style] Name = Fusion, ColorScheme = System) to Fabric,
  Leather, Plasma and VelvetMarble so their curated palettes actually apply.
  Without it the native style paints button chrome from the OS (windowsvista
  on Windows has no dark mode), making the dark palettes' light ButtonText
  unreadable on light buttons.
- Leverage the scheme-variant asset resolution: register home-dark.png and
  home-light.png in resources and add them to the qrc so the built-in home
  background also flips with the palette instead of staying static.
- Fix Leather [Palette.Inactive] Accent, which copied the Active Highlight
  color instead of the theme accent (dark #4a5f8f -> #c9995a, light
  #34508c -> #a5712f) in both palette files.
2026-09-12 20:17:02 +02:00
Lukas Brübach
e288eab0fb [Themes] Add home backgrounds for the image themes 2026-09-12 20:17:01 +02:00
Lukas Brübach
acb45af225 [Themes] Add identity default palettes for the image themes
The Fabric, Leather, Plasma and VelvetMarble themes shipped only zone art and no palette, so their chrome fell back to the bare OS palette. Each theme now ships light and dark curated defaults written in the palette editor's own conventions, with [AppColors] so the home-tab buttons keep the theme's identity over static backgrounds.

- Add palette-default-light.toml / palette-default-dark.toml for the four themes; scheme resolution follows the OS since the themes declare no color scheme.
- Tint the window/base/button chrome and bevels towards each theme's identity: navy cloth (Fabric), black-brown with brass accents (Leather), electric violet with cyan sparks (Plasma), charcoal velvet with slate marble (VelvetMarble).
- Fill [AppColors] AccentStrong / AccentSoft per theme so the home-tab button gradient matches instead of falling back to the default greens.
- Derive Disabled and Inactive groups with the same conventions as PaletteGenerator::fromAccent.
2026-09-12 20:17:01 +02:00
Lukas Brübach
21db8c34c9
[Home] Replace 'Automatic' button color with explicit theme colors default
The Automatic option gated on isBuiltInTheme(): built-in themes used the
theme's accent colors, while non-built-in themes extracted colors from
their own background art. That made the result depend on the theme's
origin rather than what the user actually sees.

Remove Automatic and expose two explicit choices: 'From theme colors'
(always the theme's identity accents, now the default) and 'Extract from
background' (always sample the painted background). Drop the now-unused
isBuiltInTheme() helper.
2026-09-12 18:00:28 +02:00
Lukas Brübach
a127ff7e1b
[Themes] Route theme writes to the user themes directory
setColorScheme()/setStyleName() and the palette editor wrote directly to
the resolved theme directory, which for built-in themes is the read-only
system (install) location. Changes therefore landed in the install dir and
were lost on upgrade.

Add ThemeManager::writableThemeDir(), which always resolves to the user
themes directory, and route all theme writes through it. The palette editor
reuses the same helper, dropping its private writability probe.
2026-09-12 18:00:28 +02:00
Lukas Brübach
7549f9c2cf
[AppColors] Address review comments
- PaletteEditorDialog::onSave(): compare whole PaletteConfig (colors and
  appColors) so a change to only AccentStrong/AccentSoft writes the file;
  add PaletteConfig::operator==.
- appColor(): derive both roles from QPalette::Highlight unconditionally.
  The Fusion palettes pin Accent to near-Window values, and QPalette::Accent
  only exists on Qt 6.6+, so keying on it made identical themes render very
  differently across Qt versions.
- themeChangedSlot(): merge the theme default's [AppColors] into a custom
  palette that predates the section instead of all-or-nothing per file;
  hasPalette() now counts an appColors-only file as a palette.
- Add Default/palette-default-light.toml so the Default theme's Light scheme
  keeps the classic greens instead of falling back to the OS accent.
- home_widget: restore the isBuiltInTheme() half of the Automatic condition;
  non-built-in themes extract button colors from their own background art.
- palette_grid_widget: use appEnum.value(i) for the role cast (3 sites),
  append appHeader to headerLabels, fix the 'Lighted' typo.
2026-09-12 18:00:28 +02:00
Lukas Brübach
03684bf41d
[Client] Add [AppColors] application palette roles
The home-tab buttons' gradient over the static theme background was hardcoded, and accent-derived fallbacks could not be themed or edited: QPalette's role set is closed, so any application-specific color has to live in Cockatrice's own palette layer.

- Add an AppColor::Role enum (AccentStrong / AccentSoft) stored on PaletteConfig and round-tripped from palette-<scheme>.toml under a new [AppColors] section.
- Cache the applied app colors in ThemeManager and expose appColor(Role) with a palette-accent-derived fallback; emit paletteChanged() from applyStyleAndPalette so previews, scheme switches and OS dark mode repaint palette-driven widgets.
- Fill both app roles in PaletteGenerator::fromAccent and surface them as a dedicated section in the palette editor.
- Drive the home-tab buttons from appColor() whenever the background source is the theme (any theme, not just built-ins).
- Ship AccentStrong / AccentSoft values in the Fusion and Default default palettes so the static home-tab buttons keep their classic greens.

# Conflicts:
#	cockatrice/src/interface/widgets/general/home_widget.cpp
2026-09-12 18:00:28 +02:00
tooomm
5d025ca0bd
Use capitalized app names (#7255)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* use capitalized app name

* update urls

* app description

* Update main.cpp
2026-09-12 17:30:46 +02:00
55 changed files with 1011 additions and 97 deletions

View file

@ -63,6 +63,8 @@
<file>resources/icons/mana/W.svg</file>
<file>resources/backgrounds/home.png</file>
<file>resources/backgrounds/home-dark.png</file>
<file>resources/backgrounds/home-light.png</file>
<file>resources/backgrounds/card_triplet.svg</file>
<file>resources/backgrounds/placeholder_printing_selector.svg</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

After

Width:  |  Height:  |  Size: 16 MiB

Before After
Before After

View file

@ -94,7 +94,7 @@ QStringMap &SoundEngine::getAvailableThemes()
QDir dir;
availableThemes.clear();
// load themes from user profile dir
// Load themes from user profile dir
dir.setPath(SettingsCache::instance().getDataPath() + "/sounds");
@ -104,7 +104,7 @@ QStringMap &SoundEngine::getAvailableThemes()
}
}
// load themes from cockatrice system dir
// Load themes from Cockatrice system dir
dir.setPath(qApp->applicationDirPath() +
#ifdef Q_OS_MAC
"/../Resources/sounds"

View file

@ -17,7 +17,7 @@ enum Format
PlainText,
/**
* This is cockatrice's native deck file format, and supports deck metadata such as banner cards and tags.
* This is Cockatrice's native deck file format, and supports deck metadata such as banner cards and tags.
* Stored as .cod files.
*/
Cockatrice

View file

@ -50,7 +50,7 @@ DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bo
result = deckList.loadFromFile_Native(&file);
if (!result) {
qCInfo(DeckLoaderLog) << "Failed to load " << fileName
<< "as cockatrice format; retrying as plain format";
<< "as Cockatrice format; retrying as plain format";
file.seek(0);
result = deckList.loadFromFile_Plain(&file, CardNameNormalizer());
fmt = DeckFileFormat::PlainText;

View file

@ -131,7 +131,7 @@ public:
static void printDeckList(QPrinter *printer, const DeckList &deckList);
/**
* Converts the given deck's file to the cockatrice file format.
* Converts the given deck's file to the Cockatrice file format.
* Uses the lastLoadInfo in the LoadedDeck to determine the current name of the file and where to save to.
* @param deck The deck to convert. Should have valid lastLoadInfo. Will update the lastLoadInfo.
* @return Whether the conversion succeeded.

View file

@ -1,6 +1,5 @@
#include "palette_editor_dialog.h"
#include "../../client/settings/cache_settings.h"
#include "../theme_manager.h"
#include "palette_generator.h"
#include "palette_grid_widget.h"
@ -11,31 +10,11 @@
#include <QDialogButtonBox>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QFrame>
#include <QGuiApplication>
#include <QLabel>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QPushButton>
#include <QStyleHints>
#include <QTimer>
#include <libcockatrice/settings/paths_settings.h>
// Probe whether a directory is truly writable by trying to create and remove a
// temporary file. QFileInfo::isWritable() on a directory is unreliable (notably
// on Windows where UAC VirtualStore can make a system dir appear writable).
static bool isDirReallyWritable(const QString &dirPath)
{
const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test");
QFile f(probe);
if (!f.open(QIODevice::WriteOnly)) {
return false;
}
f.close();
f.remove();
return true;
}
PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QString &_themeName, QWidget *parent)
: QDialog(parent), themeDirPath(_themeDirPath), themeName(_themeName)
@ -46,14 +25,7 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt
// Resolve a writable directory for saving. Built-in (Default / Fusion) and
// other read-only theme directories must be customised in the user-writable
// themes directory; otherwise the write would fail or be lost on upgrade.
if (!themeDirPath.isEmpty() && isDirReallyWritable(themeDirPath)) {
saveDir = themeDirPath;
} else {
saveDir = QDir(SettingsCache::instance().paths().getThemesPath()).absoluteFilePath(themeName);
if (!QDir().mkpath(saveDir)) {
qWarning() << "Failed to create palette save directory:" << saveDir;
}
}
saveDir = ThemeManager::writableThemeDir(themeName);
// Load both scheme configs upfront so switching is instant
loadSchemes();
@ -214,7 +186,7 @@ void PaletteEditorDialog::retranslateUi()
resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette"));
saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower()));
if (saveDir.isEmpty() || !isDirReallyWritable(saveDir)) {
if (saveDir.isEmpty() || !ThemeManager::isDirReallyWritable(saveDir)) {
saveBtn->setEnabled(false);
saveBtn->setToolTip(tr("Cannot save: this theme has no writable directory"));
}
@ -297,7 +269,7 @@ void PaletteEditorDialog::onSave()
if (it.key() == loadedScheme) {
continue;
}
if (it.value().colors == savedConfig.value(it.key()).colors) {
if (it.value() == savedConfig.value(it.key())) {
continue;
}
if (!ThemeManager::commitPalette(saveDir, it.key(), it.value())) {
@ -308,7 +280,7 @@ void PaletteEditorDialog::onSave()
}
// Commit the active scheme last so the global colour scheme matches.
if (workingConfig[loadedScheme].colors != savedConfig.value(loadedScheme).colors) {
if (workingConfig[loadedScheme] != savedConfig.value(loadedScheme)) {
if (!ThemeManager::commitPalette(saveDir, loadedScheme, workingConfig[loadedScheme])) {
QMessageBox::warning(this, tr("Save failed"),
tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), saveDir));

View file

@ -150,6 +150,17 @@ PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &sch
cfg.colors[CG::Disabled][CR::HighlightedText] = disText;
cfg.colors[CG::Inactive][CR::HighlightedText] = dark ? Qt::white : Qt::black;
// Accent: same primary hue as Highlight, so palettes derived from a
// QuickSetup accent always carry a matching Accent role.
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
set3(CR::Accent, hl, disText, hl);
#endif
// Application role colors: Strong tracks the primary accent, while Soft is
// the lightened, desaturated companion used for button-gradient highlights.
cfg.appColors[AppColor::AccentStrong] = hl;
cfg.appColors[AppColor::AccentSoft] = hsl(accent.lightness() + 60, qRound(accent.hslSaturation() * 70 / 100.0));
// BrightText
QColor bright;
if (achromatic) {

View file

@ -1,5 +1,7 @@
#include "palette_grid_widget.h"
#include "../theme_manager.h"
#include <QApplication>
#include <QGridLayout>
#include <QLabel>
@ -45,6 +47,11 @@ static const QMap<QPalette::ColorRole, const char *> ROLE_DESCRIPTIONS = {
{QPalette::Shadow, QT_TR_NOOP("Very dark shadow colour")},
};
static const QMap<AppColor::Role, const char *> APP_ROLE_DESCRIPTIONS = {
{AppColor::AccentStrong, QT_TR_NOOP("Vivid primary accent (e.g. home-tab button gradient start)")},
{AppColor::AccentSoft, QT_TR_NOOP("Lightened, desaturated accent (e.g. home-tab button gradient end)")},
};
PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)
{
scroll = new QScrollArea(this);
@ -122,6 +129,46 @@ void PaletteGridWidget::buildGrid(QWidget *host)
grid->addWidget(btn, row + 1, col + 1, Qt::AlignHCenter | Qt::AlignVCenter);
}
}
// Application color section: one ColorButton per role below the role grid.
// These are not tied to a color group, so a single button spans the row.
QMetaEnum appEnum = QMetaEnum::fromType<AppColor::Role>();
const int appHeaderRow = roles.size() + 1;
auto *appHeader = new QLabel(tr("App colors"), host);
appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette"));
QFont appHeaderFont = appHeader->font();
appHeaderFont.setBold(true);
appHeader->setFont(appHeaderFont);
appHeader->setAutoFillBackground(true);
appHeader->setContentsMargins(4, 4, 4, 4);
grid->addWidget(appHeader, appHeaderRow, 0, 1, 4);
headerLabels.append(appHeader);
for (int i = 0; i < appEnum.keyCount(); ++i) {
auto role = static_cast<AppColor::Role>(appEnum.value(i));
const int row = appHeaderRow + 1 + i;
if (i % 2 == 0) {
for (int col = 0; col < 4; ++col) {
auto *shade = new QWidget(host);
shade->setAutoFillBackground(true);
grid->addWidget(shade, row, col);
rowShadeWidgets.push_back(shade);
}
}
auto *label = new QLabel(QString(appEnum.valueToKey(role)), host);
label->setToolTip(APP_ROLE_DESCRIPTIONS.value(role, {}));
label->setContentsMargins(4, 2, 8, 2);
grid->addWidget(label, row, 0);
auto *btn = new ColorButton(host);
connect(btn, &ColorButton::colorChanged, this, [this] { emit paletteChanged(); });
appColorButtons[role] = btn;
grid->addWidget(btn, row, 1, Qt::AlignHCenter | Qt::AlignVCenter);
}
}
void PaletteGridWidget::changeEvent(QEvent *e)
@ -166,6 +213,16 @@ void PaletteGridWidget::loadPalette(const PaletteConfig &cfg)
colorButtons[group][role]->setColor(color);
}
}
QMetaEnum appEnum = QMetaEnum::fromType<AppColor::Role>();
for (int i = 0; i < appEnum.keyCount(); ++i) {
auto role = static_cast<AppColor::Role>(appEnum.value(i));
QColor color = cfg.appColors.value(role);
if (!color.isValid()) {
color = themeManager->appColor(role);
}
appColorButtons[role]->setColor(color);
}
}
PaletteConfig PaletteGridWidget::currentPaletteConfig() const
@ -176,5 +233,12 @@ PaletteConfig PaletteGridWidget::currentPaletteConfig() const
cfg.colors[group][role] = colorButtons[group][role]->getColor();
}
}
QMetaEnum appEnum = QMetaEnum::fromType<AppColor::Role>();
for (int i = 0; i < appEnum.keyCount(); ++i) {
auto role = static_cast<AppColor::Role>(appEnum.value(i));
cfg.appColors[role] = appColorButtons[role]->getColor();
}
return cfg;
}

View file

@ -31,6 +31,7 @@ private:
void refreshChromePalettes();
QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, ColorButton *>> colorButtons;
QMap<AppColor::Role, ColorButton *> appColorButtons;
QScrollArea *scroll;
QWidget *gridHost;
QVBoxLayout *layout;

View file

@ -96,7 +96,7 @@ bool ThemeConfig::save(const QString &themeDirPath) const
bool PaletteConfig::hasPalette() const
{
return !colors.isEmpty();
return !colors.isEmpty() || !appColors.isEmpty();
}
QString PaletteConfig::toToml() const
@ -133,6 +133,24 @@ QString PaletteConfig::toToml() const
out += "\n";
}
if (!appColors.isEmpty()) {
QMetaEnum appEnum = QMetaEnum::fromType<AppColor::Role>();
out += "[AppColors]\n";
for (auto it = appColors.cbegin(); it != appColors.cend(); ++it) {
const char *roleName = appEnum.valueToKey(it.key());
if (!roleName) {
continue;
}
out += QString("%1 = %2\n").arg(QString(roleName), -20).arg(it.value().name(QColor::HexArgb));
}
out += "\n";
}
return out;
}
@ -152,6 +170,7 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath)
}
QMetaEnum roleEnum = QMetaEnum::fromType<QPalette::ColorRole>();
QMetaEnum appEnum = QMetaEnum::fromType<AppColor::Role>();
QString currentSection;
QPalette::ColorGroup currentGroup = QPalette::Active;
@ -202,6 +221,26 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath)
}
}
QColor color(value);
if (!color.isValid()) {
continue;
}
if (currentSection.compare("AppColors", Qt::CaseInsensitive) == 0) {
if (key.startsWith("AppColor::")) {
key = key.mid(10);
}
int appRoleInt = appEnum.keyToValue(key.toUtf8().constData());
if (appRoleInt >= 0) {
cfg.appColors[static_cast<AppColor::Role>(appRoleInt)] = color;
}
continue;
}
if (!currentSection.startsWith("Palette", Qt::CaseInsensitive)) {
continue;
}
@ -216,11 +255,7 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath)
continue;
}
QColor color(value);
if (color.isValid()) {
cfg.colors[currentGroup][static_cast<QPalette::ColorRole>(roleInt)] = color;
}
cfg.colors[currentGroup][static_cast<QPalette::ColorRole>(roleInt)] = color;
}
return cfg;

View file

@ -3,9 +3,25 @@
#include <QColor>
#include <QMap>
#include <QObject>
#include <QPalette>
#include <QString>
// Application-specific color roles, layered on top of the fixed QPalette role
// set. Stored in the same palette-<scheme>.toml under an [AppColors] section
// and editable from the palette editor, so theme authors can control colors
// beyond what Qt's palette can express.
namespace AppColor
{
Q_NAMESPACE
enum Role
{
AccentStrong,
AccentSoft,
};
Q_ENUM_NS(Role)
} // namespace AppColor
struct ThemeConfig
{
QString colorScheme;
@ -21,7 +37,16 @@ struct ThemeConfig
struct PaletteConfig
{
QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, QColor>> colors;
QMap<AppColor::Role, QColor> appColors;
bool operator==(const PaletteConfig &rhs) const
{
return colors == rhs.colors && appColors == rhs.appColors;
}
bool operator!=(const PaletteConfig &rhs) const
{
return !(*this == rhs);
}
bool hasPalette() const;
QString toToml() const;

View file

@ -6,6 +6,7 @@
#include <QApplication>
#include <QColor>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QLibraryInfo>
#include <QMap>
@ -184,11 +185,32 @@ QString ThemeManager::assetPath(QStringView prefix) const
return resolvedPlain.isEmpty() ? prefix.toString() : resolvedPlain;
}
bool ThemeManager::isBuiltInTheme()
// Probe whether a directory is truly writable by trying to create and remove a
// temporary file. QFileInfo::isWritable() on a directory is unreliable (notably
// on Windows where UAC VirtualStore can make a system dir appear writable).
bool ThemeManager::isDirReallyWritable(const QString &dirPath)
{
const auto themeName = SettingsCache::instance().getThemeName();
const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test");
QFile f(probe);
if (!f.open(QIODevice::WriteOnly)) {
return false;
}
f.close();
f.remove();
return true;
}
return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME;
QString ThemeManager::writableThemeDir(const QString &themeName)
{
// All theme writes go to the user themes directory regardless of whether
// the resolved (system) theme directory happens to be writable. Even when a
// write would succeed in-place, routing it to the user directory keeps the
// install intact and guarantees changes survive upgrades.
const QString dirPath = QDir(SettingsCache::instance().paths().getThemesPath()).absoluteFilePath(themeName);
if (!QDir().mkpath(dirPath)) {
qWarning() << "Failed to create theme save directory:" << dirPath;
}
return dirPath;
}
// System (read-only) themes location, relative to the application binary.
@ -224,7 +246,7 @@ QStringMap &ThemeManager::getAvailableThemes()
}
}
// load themes from cockatrice system dir
// Load themes from Cockatrice system dir
dir.setPath(systemThemesBasePath());
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
@ -331,7 +353,7 @@ bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &col
void ThemeManager::setColorScheme(const QString &scheme)
{
const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName());
const QString dirPath = writableThemeDir(SettingsCache::instance().getThemeName());
ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
cfg.colorScheme = scheme;
@ -342,7 +364,7 @@ void ThemeManager::setColorScheme(const QString &scheme)
void ThemeManager::setStyleName(const QString &styleName)
{
const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName());
const QString dirPath = writableThemeDir(SettingsCache::instance().getThemeName());
ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
cfg.styleName = styleName;
@ -416,6 +438,8 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
qApp->setPalette(base);
qApp->setStyle(style);
currentAppColors = palCfg.appColors;
// Force every widget to re-polish and repaint immediately rather than
// waiting for natural expose events, which produces a patchwork of old
// and new colours during a live preview.
@ -428,6 +452,35 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
style->polish(widget);
widget->update();
}
emit paletteChanged();
}
QColor ThemeManager::appColor(AppColor::Role role) const
{
const auto it = currentAppColors.constFind(role);
if (it != currentAppColors.constEnd()) {
return it.value();
}
// QPalette::Accent was introduced in Qt 6.6 and several shipped palettes
// set it to a value barely distinguishable from Window, so it is not a
// reliable accent source. The selection highlight is the stable accent
// (Accent defaults to Highlight when unset), and deriving from it
// unconditionally keeps every Qt version rendering identically.
const QColor accent = qApp->palette().color(QPalette::Active, QPalette::Highlight);
if (role == AppColor::AccentSoft) {
constexpr int SOFT_SATURATION_PERCENT = 70;
constexpr int SOFT_LIGHTNESS_OFFSET = 60;
// Light end of the gradient: same hue, softened and lightened
return QColor::fromHsl(qMax(0, accent.hslHue()),
qBound(0, qRound(accent.hslSaturation() * SOFT_SATURATION_PERCENT / 100.0), 255),
qBound(0, accent.lightness() + SOFT_LIGHTNESS_OFFSET, 255));
}
return accent;
}
void ThemeManager::themeChangedSlot()
@ -464,8 +517,19 @@ void ThemeManager::themeChangedSlot()
// ── Load palette: custom first, then theme default ────────────────────
PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme);
if (!palette.hasPalette()) {
palette = ThemeManager::loadDefaultPaletteConfig(dirPath, themeName, activeScheme);
const PaletteConfig themeDefault = ThemeManager::loadDefaultPaletteConfig(dirPath, themeName, activeScheme);
if (palette.hasPalette()) {
// A custom palette written before [AppColors] existed carries no app
// colors; merge the theme's shipped defaults so the identity colors
// survive (hasPalette() counts an app-colors-only file as a palette,
// so those are kept wholesale and never reach here empty).
for (auto it = themeDefault.appColors.cbegin(); it != themeDefault.appColors.cend(); ++it) {
if (!palette.appColors.contains(it.key())) {
palette.appColors.insert(it.key(), it.value());
}
}
} else {
palette = themeDefault;
}
applyStyleAndPalette(themeName, themeCfg, palette, activeScheme);

View file

@ -50,6 +50,7 @@ private:
QString currentThemePath;
std::array<QBrush, Role::MaxRole + 1> brushes;
QStringMap availableThemes;
QMap<AppColor::Role, QColor> currentAppColors;
/*
Internal cache for multiple backgrounds
*/
@ -65,7 +66,16 @@ protected:
const QString &activeScheme);
public:
bool isBuiltInTheme();
// Resolves the directory to write theme changes to for the given theme
// name. The resolved theme dir (user or system) is used when writable;
// read-only system themes fall back to the user themes directory, creating
// it if needed, so customisations never get lost on upgrade.
static QString writableThemeDir(const QString &themeName);
// Probe whether a directory is truly writable by trying to create and remove
// a temporary file. QFileInfo::isWritable() on a directory is unreliable
// (notably on Windows where UAC VirtualStore can make a system dir appear
// writable).
static bool isDirReallyWritable(const QString &dirPath);
// Explicit color scheme of the theme: theme.cfg's ColorScheme setting
// (Dark/Light), falling back to the OS color scheme when it is "System".
bool isDarkMode(const QString &themeDirPath) const;
@ -115,12 +125,17 @@ public:
void reloadCurrentTheme();
void previewPalette(const PaletteConfig &cfg, const QString &scheme);
// Resolves an application color role: the theme's stored [AppColors] value
// when present, otherwise a palette-accent-derived fallback.
QColor appColor(AppColor::Role role) const;
QBrush &getBgBrush(Role zone);
QBrush getExtraBgBrush(Role zone, int zoneId = 0);
protected slots:
void themeChangedSlot();
signals:
void themeChanged();
void paletteChanged();
};
extern ThemeManager *themeManager;

View file

@ -478,13 +478,13 @@ void DlgSettings::closeEvent(QCloseEvent *event)
case Invalid:
loadErrorMessage = tr("Your card database is invalid.\n\n"
"Cockatrice may not function correctly with an invalid database\n\n"
"You may need to rerun oracle to update your card database.\n\n"
"You may need to rerun Oracle to update your card database.\n\n"
"Would you like to change your database location setting?");
break;
case VersionTooOld:
loadErrorMessage = tr("Your card database version is too old.\n\n"
"This can cause problems loading card information or images\n\n"
"Usually this can be fixed by rerunning oracle to to update your card database.\n\n"
"Usually this can be fixed by rerunning Oracle to to update your card database.\n\n"
"Would you like to change your database location setting?");
break;
case NotLoaded:

View file

@ -11,8 +11,8 @@ namespace HomeTabButtonColor
*/
enum Source
{
Automatic, ///< Extract color from background, or use theme color if no background
FromBackground, ///< Always extract color from background
FromThemeColors, ///< Use the theme's identity accent colors
FromBackground, ///< Extract colour from the background image
};
struct Entry
@ -23,7 +23,7 @@ struct Entry
inline QList<Entry> all()
{
static QList<Entry> entries = {{Automatic, QT_TR_NOOP("Automatic")},
static QList<Entry> entries = {{FromThemeColors, QT_TR_NOOP("From theme colors")},
{FromBackground, QT_TR_NOOP("Extract from background")}};
return entries;
@ -33,12 +33,12 @@ inline QList<Entry> all()
* Safely converts an int into the corresponding Source.
*
* @param value The int value
* @return The Source. Returns Source::Automatic if the value is not within range
* @return The Source. Returns Source::FromThemeColors if the value is not within range
*/
inline Source intToSource(int value)
{
if (value > FromBackground) {
return Automatic; // default
return FromThemeColors; // default
}
return static_cast<Source>(value);

View file

@ -54,6 +54,8 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
// Lambda is cleaner to read than overloading this
connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabDisplayCardNameChanged, this,
[this] { repaint(); });
connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabBackgroundDimChanged, this,
[this] { repaint(); });
connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this,
&HomeWidget::initializeBackgroundFromSource);
connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this,
@ -61,6 +63,7 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
// 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(themeManager, &ThemeManager::paletteChanged, this, &HomeWidget::updateButtonsToBackgroundColor);
connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabButtonColorChanged, this,
&HomeWidget::updateButtonsToBackgroundColor);
}
@ -105,32 +108,24 @@ void HomeWidget::loadBackgroundSourceDeck()
backgroundSourceDeck = deckOpt.has_value() ? deckOpt.value().deckList : DeckList();
}
static bool isDefaultBackgroundAndTheme()
static QPair<QColor, QColor> paletteDerivedButtonColors()
{
QString sourceId = SettingsCache::instance().appearance().getHomeTabBackgroundSource();
return themeManager->isBuiltInTheme() && BackgroundSources::fromId(sourceId) == BackgroundSources::Theme;
return {themeManager->appColor(AppColor::AccentStrong), themeManager->appColor(AppColor::AccentSoft)};
}
QPair<QColor, QColor> HomeWidget::determineButtonColor() const
{
static QPair defaultColor = {QColor::fromRgb(20, 140, 60), QColor::fromRgb(120, 200, 80)};
auto colorSource =
HomeTabButtonColor::intToSource(SettingsCache::instance().appearance().getHomeTabButtonColorSourceIndex());
switch (colorSource) {
case HomeTabButtonColor::Automatic: {
if (isDefaultBackgroundAndTheme()) {
return defaultColor;
} else {
return extractDominantColors(background);
}
}
case HomeTabButtonColor::FromThemeColors:
return paletteDerivedButtonColors();
case HomeTabButtonColor::FromBackground:
return extractDominantColors(background);
}
return defaultColor;
return paletteDerivedButtonColors();
}
void HomeWidget::setRandomCard(ExactCard &newCard)
@ -365,13 +360,15 @@ void HomeWidget::paintEvent(QPaintEvent *event)
painter.drawPixmap(topLeft, toDraw);
}
// Draw translucent black overlay with rounded corners
QRectF overlayRect(5, 5, width() - 10, height() - 10);
QPainterPath roundedRectPath;
roundedRectPath.addRoundedRect(overlayRect, 20, 20);
if (SettingsCache::instance().appearance().getHomeTabBackgroundDim()) {
// Draw translucent black overlay with rounded corners
QRectF overlayRect(5, 5, width() - 10, height() - 10);
QPainterPath roundedRectPath;
roundedRectPath.addRoundedRect(overlayRect, 20, 20);
QColor semiTransparentBlack(0, 0, 0, static_cast<int>(255 * 0.33));
painter.fillPath(roundedRectPath, semiTransparentBlack);
QColor semiTransparentBlack(0, 0, 0, static_cast<int>(255 * 0.33));
painter.fillPath(roundedRectPath, semiTransparentBlack);
}
// Card name overlay (above the attribution, bottom-right)
QString cardName;

View file

@ -132,6 +132,10 @@ AppearanceSettingsPage::AppearanceSettingsPage()
connect(&homeTabDisplayCardNameCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(),
&AppearanceSettings::setHomeTabDisplayCardName);
homeTabBackgroundDimCheckBox.setChecked(settings.appearance().getHomeTabBackgroundDim());
connect(&homeTabBackgroundDimCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(),
&AppearanceSettings::setHomeTabBackgroundDim);
for (const auto &entry : HomeTabButtonColor::all()) {
homeTabButtonColorSourceBox.addItem(QObject::tr(entry.trKey));
}
@ -148,8 +152,9 @@ AppearanceSettingsPage::AppearanceSettingsPage()
homeTabGrid->addWidget(&homeTabBackgroundShuffleFrequencyLabel, 1, 0);
homeTabGrid->addWidget(&homeTabBackgroundShuffleFrequencySpinBox, 1, 1);
homeTabGrid->addWidget(&homeTabDisplayCardNameCheckBox, 2, 0, 1, 2);
homeTabGrid->addWidget(&homeTabButtonColorSourceLabel, 3, 0);
homeTabGrid->addWidget(&homeTabButtonColorSourceBox, 3, 1);
homeTabGrid->addWidget(&homeTabBackgroundDimCheckBox, 3, 0, 1, 2);
homeTabGrid->addWidget(&homeTabButtonColorSourceLabel, 4, 0);
homeTabGrid->addWidget(&homeTabButtonColorSourceBox, 4, 1);
homeTabGroupBox = new QGroupBox;
homeTabGroupBox->setLayout(homeTabGrid);
@ -508,9 +513,12 @@ void AppearanceSettingsPage::retranslateUi()
homeTabBackgroundShuffleFrequencyLabel.setText(tr("Home tab background shuffle frequency:"));
homeTabBackgroundShuffleFrequencySpinBox.setSpecialValueText(tr("Disabled"));
homeTabDisplayCardNameCheckBox.setText(tr("Display card name of background in bottom right"));
homeTabBackgroundDimCheckBox.setText(tr("Dim the home tab background"));
homeTabBackgroundDimCheckBox.setToolTip(
tr("Draw a translucent overlay over the home tab background so buttons and text stand out"));
homeTabButtonColorSourceLabel.setText(tr("Home tab button color:"));
homeTabButtonColorSourceBox.setToolTip(
tr("Automatic: extract from background if present, otherwise use theme default"));
tr("Use the theme's identity accent colors, or extract colors from the background image"));
playmatGroupBox->setTitle(tr("Playmat settings"));
playmatVisibilityLabel.setText(tr("Playmat visibility:"));

View file

@ -41,6 +41,7 @@ private:
QLabel homeTabBackgroundShuffleFrequencyLabel;
QSpinBox homeTabBackgroundShuffleFrequencySpinBox;
QCheckBox homeTabDisplayCardNameCheckBox;
QCheckBox homeTabBackgroundDimCheckBox;
QLabel homeTabButtonColorSourceLabel;
QComboBox homeTabButtonColorSourceBox;

View file

@ -92,8 +92,8 @@
#include <libcockatrice/settings/updates_settings.h>
#define GITHUB_PAGES_URL "https://cockatrice.github.io"
#define GITHUB_CONTRIBUTORS_URL "https://github.com/Cockatrice/Cockatrice/graphs/contributors?type=c"
#define GITHUB_CONTRIBUTE_URL "https://github.com/Cockatrice/Cockatrice#cockatrice"
#define GITHUB_CONTRIBUTORS_URL "https://github.com/Cockatrice/Cockatrice/graphs/contributors"
#define GITHUB_CONTRIBUTE_URL "https://github.com/Cockatrice/Cockatrice#"
#define GITHUB_TRANSIFEX_TRANSLATORS_URL "https://github.com/Cockatrice/Cockatrice/wiki/Translator-Hall-of-Fame"
#define GITHUB_TRANSLATOR_FAQ_URL "https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ"
#define GITHUB_ISSUES_URL "https://github.com/Cockatrice/Cockatrice/issues"
@ -1050,7 +1050,7 @@ void MainWindow::createCardUpdateProcess(bool background)
if (dir.exists(binaryName)) {
updaterCmd = dir.absoluteFilePath(binaryName);
} else { // try and find the directory oracle is stored in the build directory
} else { // try and find the directory Oracle is stored in the build directory
QDir findLocalDir(dir);
findLocalDir.cdUp();
findLocalDir.cd(getCardUpdaterBinaryName());

View file

@ -231,7 +231,7 @@ int main(int argc, char *argv[])
// These values are only used by the settings loader/saver
// Wrong or outdated values are kept to not break things
QCoreApplication::setOrganizationName("Cockatrice");
QCoreApplication::setOrganizationDomain("cockatrice.de");
QCoreApplication::setOrganizationDomain("cockatrice.github.io");
QCoreApplication::setApplicationName("Cockatrice");
QCoreApplication::setApplicationVersion(VERSION_STRING);
@ -250,7 +250,7 @@ int main(int argc, char *argv[])
// Command-line parser
QCommandLineParser parser;
parser.setApplicationDescription("Cockatrice");
parser.setApplicationDescription("Cockatrice Client");
parser.addHelpOption();
parser.addVersionOption();
@ -350,8 +350,8 @@ int main(int argc, char *argv[])
qCInfo(MainLog) << "MainWindow constructor finished";
ui.setWindowIcon(themePixmap(QStringLiteral("cockatrice")));
// set name of the app desktop file; used by wayland to load the window icon
QGuiApplication::setDesktopFileName("cockatrice");
// Set name of the app desktop file; used by wayland to load the window icon
QGuiApplication::setDesktopFileName("Cockatrice");
SettingsCache::instance().network().setClientID(generateClientID());

View file

@ -61,3 +61,7 @@ ToolTipBase = #ffffffdc
ToolTipText = #ff000000
PlaceholderText = #6effffff
[AppColors]
AccentStrong = #ff148c3c
AccentSoft = #ff78c850

View file

@ -0,0 +1,67 @@
[Palette]
WindowText = #ff000000
Button = #fff0f0f0
Light = #ffffffff
Midlight = #ffe3e3e3
Dark = #ffa0a0a0
Mid = #ffa0a0a0
Text = #ff000000
BrightText = #ffffffff
ButtonText = #ff000000
Base = #ffffffff
Window = #fff0f0f0
Shadow = #ff696969
HighlightedText = #ffffffff
Link = #ff0d5f28
LinkVisited = #ff08401b
AlternateBase = #ffe9e7e3
ToolTipBase = #ffffffdc
ToolTipText = #ff000000
PlaceholderText = #80000000
[Palette.Disabled]
WindowText = #ff787878
Button = #fff0f0f0
Light = #ffffffff
Midlight = #fff7f7f7
Dark = #ffa0a0a0
Mid = #ffa0a0a0
Text = #ff787878
BrightText = #ffffffff
ButtonText = #ff787878
Base = #fff0f0f0
Window = #fff0f0f0
Shadow = #ff000000
HighlightedText = #ffffffff
Link = #ff0000ff
LinkVisited = #ffff00ff
AlternateBase = #fff7f7f7
ToolTipBase = #ffffffdc
ToolTipText = #ff000000
PlaceholderText = #80000000
[Palette.Inactive]
WindowText = #ff000000
Button = #fff0f0f0
Light = #ffffffff
Midlight = #ffe3e3e3
Dark = #ffa0a0a0
Mid = #ffa0a0a0
Text = #ff000000
BrightText = #ffffffff
ButtonText = #ff000000
Base = #ffffffff
Window = #fff0f0f0
Shadow = #ff696969
HighlightedText = #ff000000
Link = #ff0d5f28
LinkVisited = #ff08401b
AlternateBase = #ffe9e7e3
ToolTipBase = #ffffffdc
ToolTipText = #ff000000
PlaceholderText = #80000000
[AppColors]
AccentStrong = #ff148c3c
AccentSoft = #ff78c850

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

View file

@ -0,0 +1,74 @@
# Fabric — dark identity palette (navy-cloth chrome)
[Palette]
WindowText = #e8ecf2
Button = #2b374a
Light = #3a4759
Midlight = #333f50
Dark = #141a23
Mid = #252c38
Text = #e8ecf2
BrightText = #8ec2ff
ButtonText = #f2f4f8
Base = #222c3d
Window = #18202e
Shadow = #0b0f15
Highlight = #4a82e6
HighlightedText = #ffffff
Link = #7fb0f2
LinkVisited = #a98ad9
AlternateBase = #1d2636
ToolTipBase = #2b3546
ToolTipText = #f2f4f8
PlaceholderText = #6ee8ecf2
Accent = #4a82e6
[Palette.Disabled]
WindowText = #9d9d9d
Button = #18202e
Light = #3a4759
Midlight = #333f50
Dark = #141a23
Mid = #252c38
Text = #9d9d9d
BrightText = #8ec2ff
ButtonText = #787878
Base = #18202e
Window = #18202e
Shadow = #0b0f15
Highlight = #222d40
HighlightedText = #9d9d9d
Link = #308cc6
LinkVisited = #b450ff
AlternateBase = #1d2636
ToolTipBase = #2b3546
ToolTipText = #f2f4f8
PlaceholderText = #6ee8ecf2
Accent = #9d9d9d
[Palette.Inactive]
WindowText = #e8ecf2
Button = #2b374a
Light = #3a4759
Midlight = #333f50
Dark = #141a23
Mid = #252c38
Text = #e8ecf2
BrightText = #8ec2ff
ButtonText = #f2f4f8
Base = #222c3d
Window = #18202e
Shadow = #0b0f15
Highlight = #222d40
HighlightedText = #ffffff
Link = #7fb0f2
LinkVisited = #a98ad9
AlternateBase = #1d2636
ToolTipBase = #2b3546
ToolTipText = #f2f4f8
PlaceholderText = #6ee8ecf2
Accent = #4a82e6
[AppColors]
AccentStrong = #3a6fd6
AccentSoft = #8fb8f2

View file

@ -0,0 +1,74 @@
# Fabric — light identity palette (navy-cloth chrome)
[Palette]
WindowText = #1c2330
Button = #c8d6ea
Light = #ffffff
Midlight = #b9c6dc
Dark = #8fa0bd
Mid = #a3b3cd
Text = #1c2330
BrightText = #1d4fa0
ButtonText = #141c29
Base = #f6f8fc
Window = #dbe3ef
Shadow = #5a6a85
Highlight = #2f6fd6
HighlightedText = #ffffff
Link = #2555b0
LinkVisited = #6a4bb8
AlternateBase = #e9eef7
ToolTipBase = #e9eff8
ToolTipText = #141c29
PlaceholderText = #7a1c2330
Accent = #2f6fd6
[Palette.Disabled]
WindowText = #787878
Button = #dbe3ef
Light = #ffffff
Midlight = #b9c6dc
Dark = #8fa0bd
Mid = #a3b3cd
Text = #787878
BrightText = #1d4fa0
ButtonText = #969696
Base = #dbe3ef
Window = #dbe3ef
Shadow = #5a6a85
Highlight = #cdd8e9
HighlightedText = #787878
Link = #0000ff
LinkVisited = #ff00ff
AlternateBase = #e9eef7
ToolTipBase = #e9eff8
ToolTipText = #141c29
PlaceholderText = #7a1c2330
Accent = #787878
[Palette.Inactive]
WindowText = #1c2330
Button = #c8d6ea
Light = #ffffff
Midlight = #b9c6dc
Dark = #8fa0bd
Mid = #a3b3cd
Text = #1c2330
BrightText = #1d4fa0
ButtonText = #141c29
Base = #f6f8fc
Window = #dbe3ef
Shadow = #5a6a85
Highlight = #cdd8e9
HighlightedText = #000000
Link = #2555b0
LinkVisited = #6a4bb8
AlternateBase = #e9eef7
ToolTipBase = #e9eff8
ToolTipText = #141c29
PlaceholderText = #7a1c2330
Accent = #2f6fd6
[AppColors]
AccentStrong = #3a6fd6
AccentSoft = #8fb8f2

View file

@ -0,0 +1,5 @@
[Appearance]
ColorScheme = System
[Style]
Name = Fusion

View file

@ -67,3 +67,7 @@ ToolTipText = #ffd4d4d4
PlaceholderText = #80ffffff
Accent = #ff1e1e1e
[AppColors]
AccentStrong = #ff148c3c
AccentSoft = #ff78c850

View file

@ -67,3 +67,7 @@ ToolTipText = #ff000000
PlaceholderText = #80000000
Accent = #fff0f0f0
[AppColors]
AccentStrong = #ff148c3c
AccentSoft = #ff78c850

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View file

@ -0,0 +1,74 @@
# Leather — dark identity palette (black-brown chrome with brass accents)
[Palette]
WindowText = #eee9e2
Button = #332d26
Light = #4a4239
Midlight = #3d3730
Dark = #131009
Mid = #26221c
Text = #eee9e2
BrightText = #e3c27e
ButtonText = #f5f1ea
Base = #26221d
Window = #1d1a16
Shadow = #0d0b07
Highlight = #4a5f8f
HighlightedText = #ffffff
Link = #cfa263
LinkVisited = #9a7db8
AlternateBase = #211d19
ToolTipBase = #37302a
ToolTipText = #f5f1ea
PlaceholderText = #6eeee9e2
Accent = #c9995a
[Palette.Disabled]
WindowText = #9d9d9d
Button = #1d1a16
Light = #4a4239
Midlight = #3d3730
Dark = #131009
Mid = #26221c
Text = #9d9d9d
BrightText = #e3c27e
ButtonText = #787878
Base = #1d1a16
Window = #1d1a16
Shadow = #0d0b07
Highlight = #2d2822
HighlightedText = #9d9d9d
Link = #308cc6
LinkVisited = #b450ff
AlternateBase = #211d19
ToolTipBase = #37302a
ToolTipText = #f5f1ea
PlaceholderText = #6eeee9e2
Accent = #9d9d9d
[Palette.Inactive]
WindowText = #eee9e2
Button = #332d26
Light = #4a4239
Midlight = #3d3730
Dark = #131009
Mid = #26221c
Text = #eee9e2
BrightText = #e3c27e
ButtonText = #f5f1ea
Base = #26221d
Window = #1d1a16
Shadow = #0d0b07
Highlight = #2d2822
HighlightedText = #ffffff
Link = #cfa263
LinkVisited = #9a7db8
AlternateBase = #211d19
ToolTipBase = #37302a
ToolTipText = #f5f1ea
PlaceholderText = #6eeee9e2
Accent = #c9995a
[AppColors]
AccentStrong = #b7823b
AccentSoft = #e4c58f

View file

@ -0,0 +1,74 @@
# Leather — light identity palette (black-brown chrome with brass accents)
[Palette]
WindowText = #2a2114
Button = #dfcbb0
Light = #ffffff
Midlight = #c6b08c
Dark = #a18a64
Mid = #b29a74
Text = #2a2114
BrightText = #7a531c
ButtonText = #1f1710
Base = #fdf8ee
Window = #f0e2cb
Shadow = #5d4d33
Highlight = #34508c
HighlightedText = #ffffff
Link = #8a5e1e
LinkVisited = #6b5190
AlternateBase = #f5ebd7
ToolTipBase = #fff7e8
ToolTipText = #1f1710
PlaceholderText = #7a2a2114
Accent = #a5712f
[Palette.Disabled]
WindowText = #787878
Button = #f0e2cb
Light = #ffffff
Midlight = #c6b08c
Dark = #a18a64
Mid = #b29a74
Text = #787878
BrightText = #7a531c
ButtonText = #969696
Base = #f0e2cb
Window = #f0e2cb
Shadow = #5d4d33
Highlight = #ecd9bb
HighlightedText = #787878
Link = #0000ff
LinkVisited = #ff00ff
AlternateBase = #f5ebd7
ToolTipBase = #fff7e8
ToolTipText = #1f1710
PlaceholderText = #7a2a2114
Accent = #787878
[Palette.Inactive]
WindowText = #2a2114
Button = #dfcbb0
Light = #ffffff
Midlight = #c6b08c
Dark = #a18a64
Mid = #b29a74
Text = #2a2114
BrightText = #7a531c
ButtonText = #1f1710
Base = #fdf8ee
Window = #f0e2cb
Shadow = #5d4d33
Highlight = #ecd9bb
HighlightedText = #000000
Link = #8a5e1e
LinkVisited = #6b5190
AlternateBase = #f5ebd7
ToolTipBase = #fff7e8
ToolTipText = #1f1710
PlaceholderText = #7a2a2114
Accent = #a5712f
[AppColors]
AccentStrong = #b7823b
AccentSoft = #e4c58f

View file

@ -0,0 +1,5 @@
[Appearance]
ColorScheme = System
[Style]
Name = Fusion

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View file

@ -0,0 +1,74 @@
# Plasma — dark identity palette (electric violet chrome with cyan sparks)
[Palette]
WindowText = #eeebff
Button = #2a2440
Light = #453d63
Midlight = #383252
Dark = #0e0c16
Mid = #211e31
Text = #eeebff
BrightText = #7fd7ff
ButtonText = #f6f4ff
Base = #1e1a2e
Window = #151220
Shadow = #08070e
Highlight = #6a4df0
HighlightedText = #ffffff
Link = #37c7e8
LinkVisited = #9b7aff
AlternateBase = #1a1726
ToolTipBase = #2c2745
ToolTipText = #f6f4ff
PlaceholderText = #6eeeebff
Accent = #6a4df0
[Palette.Disabled]
WindowText = #9d9d9d
Button = #151220
Light = #453d63
Midlight = #383252
Dark = #0e0c16
Mid = #211e31
Text = #9d9d9d
BrightText = #7fd7ff
ButtonText = #787878
Base = #151220
Window = #151220
Shadow = #08070e
Highlight = #211c32
HighlightedText = #9d9d9d
Link = #308cc6
LinkVisited = #b450ff
AlternateBase = #1a1726
ToolTipBase = #2c2745
ToolTipText = #f6f4ff
PlaceholderText = #6eeeebff
Accent = #9d9d9d
[Palette.Inactive]
WindowText = #eeebff
Button = #2a2440
Light = #453d63
Midlight = #383252
Dark = #0e0c16
Mid = #211e31
Text = #eeebff
BrightText = #7fd7ff
ButtonText = #f6f4ff
Base = #1e1a2e
Window = #151220
Shadow = #08070e
Highlight = #211c32
HighlightedText = #ffffff
Link = #37c7e8
LinkVisited = #9b7aff
AlternateBase = #1a1726
ToolTipBase = #2c2745
ToolTipText = #f6f4ff
PlaceholderText = #6eeeebff
Accent = #6a4df0
[AppColors]
AccentStrong = #5b3ee0
AccentSoft = #a28bf7

View file

@ -0,0 +1,74 @@
# Plasma — light identity palette (electric violet chrome with cyan sparks)
[Palette]
WindowText = #1d1830
Button = #cfc6f0
Light = #ffffff
Midlight = #b7adde
Dark = #9589c8
Mid = #a49ad2
Text = #1d1830
BrightText = #10406e
ButtonText = #120d26
Base = #f9f7ff
Window = #e3dcf7
Shadow = #554c85
Highlight = #5a3ee2
HighlightedText = #ffffff
Link = #0f8fb5
LinkVisited = #6a45d6
AlternateBase = #ece8fa
ToolTipBase = #f5f3ff
ToolTipText = #120d26
PlaceholderText = #7a1d1830
Accent = #5a3ee2
[Palette.Disabled]
WindowText = #787878
Button = #e3dcf7
Light = #ffffff
Midlight = #b7adde
Dark = #9589c8
Mid = #a49ad2
Text = #787878
BrightText = #10406e
ButtonText = #969696
Base = #e3dcf7
Window = #e3dcf7
Shadow = #554c85
Highlight = #d6ccf3
HighlightedText = #787878
Link = #0000ff
LinkVisited = #ff00ff
AlternateBase = #ece8fa
ToolTipBase = #f5f3ff
ToolTipText = #120d26
PlaceholderText = #7a1d1830
Accent = #787878
[Palette.Inactive]
WindowText = #1d1830
Button = #cfc6f0
Light = #ffffff
Midlight = #b7adde
Dark = #9589c8
Mid = #a49ad2
Text = #1d1830
BrightText = #10406e
ButtonText = #120d26
Base = #f9f7ff
Window = #e3dcf7
Shadow = #554c85
Highlight = #d6ccf3
HighlightedText = #000000
Link = #0f8fb5
LinkVisited = #6a45d6
AlternateBase = #ece8fa
ToolTipBase = #f5f3ff
ToolTipText = #120d26
PlaceholderText = #7a1d1830
Accent = #5a3ee2
[AppColors]
AccentStrong = #5b3ee0
AccentSoft = #a28bf7

View file

@ -0,0 +1,5 @@
[Appearance]
ColorScheme = System
[Style]
Name = Fusion

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View file

@ -0,0 +1,74 @@
# VelvetMarble — dark identity palette (charcoal-velvet chrome with slate marble)
[Palette]
WindowText = #e8eaee
Button = #262a31
Light = #3a3f47
Midlight = #31363d
Dark = #0e1013
Mid = #1f2329
Text = #e8eaee
BrightText = #c3d8f2
ButtonText = #f2f3f6
Base = #1b1e23
Window = #131519
Shadow = #08090b
Highlight = #8fa6c0
HighlightedText = #101318
Link = #a9c6e8
LinkVisited = #b9a8d9
AlternateBase = #171a1f
ToolTipBase = #2d323a
ToolTipText = #f2f3f6
PlaceholderText = #6ee8eaee
Accent = #8fa6c0
[Palette.Disabled]
WindowText = #9d9d9d
Button = #131519
Light = #3a3f47
Midlight = #31363d
Dark = #0e1013
Mid = #1f2329
Text = #9d9d9d
BrightText = #c3d8f2
ButtonText = #787878
Base = #131519
Window = #131519
Shadow = #08090b
Highlight = #1f2229
HighlightedText = #9d9d9d
Link = #308cc6
LinkVisited = #b450ff
AlternateBase = #171a1f
ToolTipBase = #2d323a
ToolTipText = #f2f3f6
PlaceholderText = #6ee8eaee
Accent = #9d9d9d
[Palette.Inactive]
WindowText = #e8eaee
Button = #262a31
Light = #3a3f47
Midlight = #31363d
Dark = #0e1013
Mid = #1f2329
Text = #e8eaee
BrightText = #c3d8f2
ButtonText = #f2f3f6
Base = #1b1e23
Window = #131519
Shadow = #08090b
Highlight = #1f2229
HighlightedText = #ffffff
Link = #a9c6e8
LinkVisited = #b9a8d9
AlternateBase = #171a1f
ToolTipBase = #2d323a
ToolTipText = #f2f3f6
PlaceholderText = #6ee8eaee
Accent = #8fa6c0
[AppColors]
AccentStrong = #54687e
AccentSoft = #9db0c4

View file

@ -0,0 +1,74 @@
# VelvetMarble — light identity palette (charcoal-velvet chrome with slate marble)
[Palette]
WindowText = #1b1d21
Button = #c7d1dd
Light = #ffffff
Midlight = #b3becb
Dark = #93a0b1
Mid = #a1adbc
Text = #1b1d21
BrightText = #26465f
ButtonText = #121418
Base = #f7f9fb
Window = #d9e0ea
Shadow = #57626f
Highlight = #54687e
HighlightedText = #ffffff
Link = #2e4d6b
LinkVisited = #5d4a78
AlternateBase = #e6ebf2
ToolTipBase = #f2f4f6
ToolTipText = #121418
PlaceholderText = #7a1b1d21
Accent = #54687e
[Palette.Disabled]
WindowText = #787878
Button = #d9e0ea
Light = #ffffff
Midlight = #b3becb
Dark = #93a0b1
Mid = #a1adbc
Text = #787878
BrightText = #26465f
ButtonText = #969696
Base = #d9e0ea
Window = #d9e0ea
Shadow = #57626f
Highlight = #ccd5e3
HighlightedText = #787878
Link = #0000ff
LinkVisited = #ff00ff
AlternateBase = #e6ebf2
ToolTipBase = #f2f4f6
ToolTipText = #121418
PlaceholderText = #7a1b1d21
Accent = #787878
[Palette.Inactive]
WindowText = #1b1d21
Button = #c7d1dd
Light = #ffffff
Midlight = #b3becb
Dark = #93a0b1
Mid = #a1adbc
Text = #1b1d21
BrightText = #26465f
ButtonText = #121418
Base = #f7f9fb
Window = #d9e0ea
Shadow = #57626f
Highlight = #ccd5e3
HighlightedText = #000000
Link = #2e4d6b
LinkVisited = #5d4a78
AlternateBase = #e6ebf2
ToolTipBase = #f2f4f6
ToolTipText = #121418
PlaceholderText = #7a1b1d21
Accent = #54687e
[AppColors]
AccentStrong = #54687e
AccentSoft = #9db0c4

View file

@ -0,0 +1,5 @@
[Appearance]
ColorScheme = System
[Style]
Name = Fusion

View file

@ -70,6 +70,17 @@ void AppearanceSettings::setHomeTabDisplayCardName(bool _displayCardName)
emit homeTabDisplayCardNameChanged();
}
bool AppearanceSettings::getHomeTabBackgroundDim() const
{
return getValue("homeTabBackgroundDim", QString(), QString(), true).toBool();
}
void AppearanceSettings::setHomeTabBackgroundDim(bool _dimBackground)
{
setValue(_dimBackground, "homeTabBackgroundDim");
emit homeTabBackgroundDimChanged();
}
int AppearanceSettings::getHomeTabButtonColorSourceIndex() const
{
return getValue("homeTabButtonColorSource", "", "", 0).toInt();

View file

@ -27,6 +27,8 @@ public:
void setHomeTabBackgroundShuffleFrequency(int _frequency);
[[nodiscard]] bool getHomeTabDisplayCardName() const;
void setHomeTabDisplayCardName(bool _displayCardName);
[[nodiscard]] bool getHomeTabBackgroundDim() const;
void setHomeTabBackgroundDim(bool _dimBackground);
[[nodiscard]] int getHomeTabButtonColorSourceIndex() const;
void setHomeTabButtonColorSourceIndex(int index);
@ -36,6 +38,7 @@ signals:
void homeTabBackgroundSourceChanged();
void homeTabBackgroundShuffleFrequencyChanged();
void homeTabDisplayCardNameChanged();
void homeTabBackgroundDimChanged();
void homeTabButtonColorChanged();
public:

View file

@ -50,8 +50,8 @@ int main(int argc, char *argv[])
QApplication app(argc, argv);
QCoreApplication::setOrganizationName("Cockatrice");
QCoreApplication::setOrganizationDomain("cockatrice");
// this can't be changed, as it influences the default save path for cards.xml
QCoreApplication::setOrganizationDomain("Cockatrice");
// This can't be changed, as it influences the default save path for cards.xml
QCoreApplication::setApplicationName("Cockatrice");
// If the program is opened with the -s flag, it will only do spoilers. Otherwise it will do MTGJSON/Tokens
@ -83,7 +83,7 @@ int main(int argc, char *argv[])
QIcon icon("theme:appicon.svg");
wizard.setWindowIcon(icon);
// set name of the app desktop file; used by wayland to load the window icon
QGuiApplication::setDesktopFileName("oracle");
QGuiApplication::setDesktopFileName("Oracle");
wizard.show();

View file

@ -796,7 +796,7 @@ void SaveSetsPage::retranslateUi()
{
setTitle(tr("Sets imported"));
if (wizard()->downloadedPlainXml) {
setSubTitle(tr("A cockatrice database file of %1 MB has been downloaded.")
setSubTitle(tr("A Cockatrice card database file of %1 MB has been downloaded.")
.arg(qRound(wizard()->xmlData.size() / 1000000.0)));
} else {
setSubTitle(tr("The following sets have been found:"));

View file

@ -20,7 +20,7 @@
* Note that "...enters tapped unless..." returns false.
*
* @param name The name of the card
* @param text The oracle text of the card
* @param text The Oracle text of the card
*/
bool parseCipt(const QString &name, const QString &text)
{

View file

@ -54,7 +54,7 @@ using ScanProgressCallback = std::function<void(qsizetype bytesRead, qsizetype t
* @brief Scans a full MTGJSON document without materializing the JSON tree.
*
* Splits the top-level "data" object into per-set byte ranges and reads each
* set's metadata directly from the raw bytes. The oracle importer can then
* set's metadata directly from the raw bytes. The Oracle importer can then
* parse one set at a time during import, keeping peak memory far below a single
* QJsonDocument::fromJson() over the whole file.
*

View file

@ -86,9 +86,10 @@ bool Servatrice_DatabaseInterface::openDatabase()
<< dbversion << "to version" << expectedversion;
return false;
} else if (dbversion > expectedversion) {
qCCritical(DatabaseInterfaceLog) << poolStr << "Error opening database: the database schema version"
<< dbversion << "is too new, you need to update servatrice"
<< "(this servatrice actually uses version" << expectedversion << ")";
qCCritical(DatabaseInterfaceLog)
<< poolStr << "Error opening database: the database schema version" << dbversion
<< "is too new, you need to update Servatrice" << "(Currently running Servatrice actually uses version"
<< expectedversion << ")";
return false;
}
} else {

View file

@ -386,6 +386,19 @@ TEST_F(SettingsDefaultsTest, Appearance_HomeTabDisplayCardName_Default)
ASSERT_EQ(s.getHomeTabDisplayCardName(), true);
}
TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundDim_Default)
{
AppearanceSettings s(settingsPath, nullptr);
ASSERT_EQ(s.getHomeTabBackgroundDim(), true);
}
TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundDim_SetAndGet)
{
AppearanceSettings s(settingsPath, nullptr);
s.setHomeTabBackgroundDim(false);
ASSERT_EQ(s.getHomeTabBackgroundDim(), false);
}
// --- InterfaceSettings ---
TEST_F(SettingsDefaultsTest, Interface_ShowStatusBar_Default)