[Icons] Add Hidden and Marked semantic color families

Two game concepts had no hue of their own. Hidden information (a face-down
play, peeking at a card face) now has violet, and the marks a player leaves on a
card (annotation, draw arrow) now share amber instead of splitting blue and red
for the same job.

Both families follow the existing three-level ladder: authored roles per theme,
scheme defaults otherwise, an emphasis mapping, palette-editor rows and generator
seeding. The defaults clear the 4.5:1 floor on the representative platform
surfaces and keep the saturation ordering the emphasis tests assert; the hue
tests pin violet's blue-red-green dominance and amber's red-green-blue one, and
every preview sheet now renders the two families.

The hover panel grid preview is added alongside: it renders the real pages
(section headers, two-column cells, glyph over label) so the whole map can be
judged at once instead of glyph by glyph.
This commit is contained in:
Lukas Brübach 2026-11-08 20:36:21 +01:00
parent cc9c93eb57
commit 8847da74e2
10 changed files with 411 additions and 9 deletions

View file

@ -178,12 +178,13 @@ PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &sch
cfg.appColors[AppColor::IconStrong] = ThemeIconInk::forEmphasis(iconInk, iconSurface, IconEmphasis::Primary);
cfg.appColors[AppColor::IconSoft] = ThemeIconInk::forEmphasis(iconInk, iconSurface, IconEmphasis::Quiet);
// Semantic roles stay independent of the accent: a generated palette must
// still paint add/ready green, remove/not-ready red, and the neutral
// information state blue.
// still paint add/ready green, remove/not-ready red, information blue,
// hidden information violet and marks amber.
for (const AppColor::Role role :
{AppColor::Positive, AppColor::PositiveStrong, AppColor::PositiveSoft, AppColor::Negative,
AppColor::NegativeStrong, AppColor::NegativeSoft, AppColor::Informational, AppColor::InformationalStrong,
AppColor::InformationalSoft}) {
AppColor::InformationalSoft, AppColor::Hidden, AppColor::HiddenStrong, AppColor::HiddenSoft, AppColor::Marked,
AppColor::MarkedStrong, AppColor::MarkedSoft}) {
cfg.appColors[role] = defaultAppColor(role, dark);
}

View file

@ -83,6 +83,13 @@ static const QMap<AppColor::Role, const char *> APP_ROLE_DESCRIPTIONS = {
"Informational state that is neither a gain nor a loss (face-down, exile)")},
{AppColor::InformationalStrong, QT_TRANSLATE_NOOP("PaletteGridWidget", "Informational state at primary emphasis")},
{AppColor::InformationalSoft, QT_TRANSLATE_NOOP("PaletteGridWidget", "Informational state at quiet emphasis")},
{AppColor::Hidden,
QT_TRANSLATE_NOOP("PaletteGridWidget", "Hidden information (face-down play, peek at the card face)")},
{AppColor::HiddenStrong, QT_TRANSLATE_NOOP("PaletteGridWidget", "Hidden information at primary emphasis")},
{AppColor::HiddenSoft, QT_TRANSLATE_NOOP("PaletteGridWidget", "Hidden information at quiet emphasis")},
{AppColor::Marked, QT_TRANSLATE_NOOP("PaletteGridWidget", "Marks left on a card (annotation, draw arrow)")},
{AppColor::MarkedStrong, QT_TRANSLATE_NOOP("PaletteGridWidget", "Marks on a card at primary emphasis")},
{AppColor::MarkedSoft, QT_TRANSLATE_NOOP("PaletteGridWidget", "Marks on a card at quiet emphasis")},
};
PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)

View file

@ -491,6 +491,24 @@ QColor defaultAppColor(AppColor::Role role, bool dark)
return QColor(dark ? QStringLiteral("#69aeff") : QStringLiteral("#0a4fb5"));
case AppColor::InformationalSoft:
return QColor(dark ? QStringLiteral("#9db2cd") : QStringLiteral("#526a8a"));
// Hidden is the game's hidden-information cluster: a face-down play and
// peeking at a card face. Violet keeps it apart from the blue
// Informational family, which carries public information (reveal).
case AppColor::Hidden:
return QColor(dark ? QStringLiteral("#c6a5fa") : QStringLiteral("#6d28d9"));
case AppColor::HiddenStrong:
return QColor(dark ? QStringLiteral("#c99bff") : QStringLiteral("#4c0f9e"));
case AppColor::HiddenSoft:
return QColor(dark ? QStringLiteral("#b3a6c9") : QStringLiteral("#6a5a8c"));
// Marked is the marks a player leaves on a card: an annotation or a
// draw arrow. Amber names a persistent, removable mark rather than a
// gain or a loss, so annotation and arrow finally share a colour.
case AppColor::Marked:
return QColor(dark ? QStringLiteral("#e2a63a") : QStringLiteral("#9a5b05"));
case AppColor::MarkedStrong:
return QColor(dark ? QStringLiteral("#ffb020") : QStringLiteral("#8a4f02"));
case AppColor::MarkedSoft:
return QColor(dark ? QStringLiteral("#c2ab7d") : QStringLiteral("#7b6a4a"));
}
return {};

View file

@ -16,10 +16,10 @@
//
// The icon families carry one role per emphasis level. Icon is the neutral
// glyph ink; IconStrong and IconSoft are its primary and quiet levels. Positive,
// Negative and Informational follow the same pattern, so an emphasis never has
// to be computed from a hue at paint time: the theme authors all three levels.
// Naming a variant explicitly (`IconStrong`) pins that level; naming the family
// (`Icon`) lets the caller's emphasis choose.
// Negative, Informational, Hidden and Marked follow the same pattern, so an
// emphasis never has to be computed from a hue at paint time: the theme authors
// all three levels. Naming a variant explicitly (`IconStrong`) pins that level;
// naming the family (`Icon`) lets the caller's emphasis choose.
namespace AppColor
{
Q_NAMESPACE
@ -39,6 +39,12 @@ enum Role
Informational,
InformationalStrong,
InformationalSoft,
Hidden,
HiddenStrong,
HiddenSoft,
Marked,
MarkedStrong,
MarkedSoft,
};
Q_ENUM_NS(Role)
} // namespace AppColor

View file

@ -58,6 +58,14 @@ AppColor::Role emphasisRole(AppColor::Role role, IconEmphasis emphasis)
return emphasis == IconEmphasis::Primary ? AppColor::InformationalStrong
: emphasis == IconEmphasis::Quiet ? AppColor::InformationalSoft
: AppColor::Informational;
case AppColor::Hidden:
return emphasis == IconEmphasis::Primary ? AppColor::HiddenStrong
: emphasis == IconEmphasis::Quiet ? AppColor::HiddenSoft
: AppColor::Hidden;
case AppColor::Marked:
return emphasis == IconEmphasis::Primary ? AppColor::MarkedStrong
: emphasis == IconEmphasis::Quiet ? AppColor::MarkedSoft
: AppColor::Marked;
default:
return role;
}

View file

@ -686,6 +686,12 @@ QColor ThemeManager::appColor(AppColor::Role role) const
case AppColor::Informational:
case AppColor::InformationalStrong:
case AppColor::InformationalSoft:
case AppColor::Hidden:
case AppColor::HiddenStrong:
case AppColor::HiddenSoft:
case AppColor::Marked:
case AppColor::MarkedStrong:
case AppColor::MarkedSoft:
// Semantic colors are independent of the accent: a missing role must
// not silently paint "remove" in the theme's highlight color.
return defaultAppColor(role, isDarkModeActive());