mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-12 04:50:01 -08:00
[Icons] Add a Movement semantic color family
A card changing position inside the library is neither a gain, a loss nor information, so it had no hue. Movement names it with teal and covers the three library placements (top, bottom, X from top). It follows the existing 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, keep strong > base > soft saturation, and keep green > blue > red so teal cannot be mistaken for the green gain family.
This commit is contained in:
parent
8847da74e2
commit
2567672556
10 changed files with 63 additions and 8 deletions
|
|
@ -179,12 +179,13 @@ PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &sch
|
||||||
cfg.appColors[AppColor::IconSoft] = ThemeIconInk::forEmphasis(iconInk, iconSurface, IconEmphasis::Quiet);
|
cfg.appColors[AppColor::IconSoft] = ThemeIconInk::forEmphasis(iconInk, iconSurface, IconEmphasis::Quiet);
|
||||||
// Semantic roles stay independent of the accent: a generated palette must
|
// Semantic roles stay independent of the accent: a generated palette must
|
||||||
// still paint add/ready green, remove/not-ready red, information blue,
|
// still paint add/ready green, remove/not-ready red, information blue,
|
||||||
// hidden information violet and marks amber.
|
// hidden information violet, marks amber and movement teal.
|
||||||
for (const AppColor::Role role :
|
for (const AppColor::Role role :
|
||||||
{AppColor::Positive, AppColor::PositiveStrong, AppColor::PositiveSoft, AppColor::Negative,
|
{AppColor::Positive, AppColor::PositiveStrong, AppColor::PositiveSoft, AppColor::Negative,
|
||||||
AppColor::NegativeStrong, AppColor::NegativeSoft, AppColor::Informational, AppColor::InformationalStrong,
|
AppColor::NegativeStrong, AppColor::NegativeSoft, AppColor::Informational, AppColor::InformationalStrong,
|
||||||
AppColor::InformationalSoft, AppColor::Hidden, AppColor::HiddenStrong, AppColor::HiddenSoft, AppColor::Marked,
|
AppColor::InformationalSoft, AppColor::Hidden, AppColor::HiddenStrong, AppColor::HiddenSoft, AppColor::Marked,
|
||||||
AppColor::MarkedStrong, AppColor::MarkedSoft}) {
|
AppColor::MarkedStrong, AppColor::MarkedSoft, AppColor::Movement, AppColor::MovementStrong,
|
||||||
|
AppColor::MovementSoft}) {
|
||||||
cfg.appColors[role] = defaultAppColor(role, dark);
|
cfg.appColors[role] = defaultAppColor(role, dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,10 @@ static const QMap<AppColor::Role, const char *> APP_ROLE_DESCRIPTIONS = {
|
||||||
{AppColor::Marked, QT_TRANSLATE_NOOP("PaletteGridWidget", "Marks left on a card (annotation, draw arrow)")},
|
{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::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")},
|
{AppColor::MarkedSoft, QT_TRANSLATE_NOOP("PaletteGridWidget", "Marks on a card at quiet emphasis")},
|
||||||
|
{AppColor::Movement,
|
||||||
|
QT_TRANSLATE_NOOP("PaletteGridWidget", "Cards moving without a gain or a loss (library placements)")},
|
||||||
|
{AppColor::MovementStrong, QT_TRANSLATE_NOOP("PaletteGridWidget", "Movement at primary emphasis")},
|
||||||
|
{AppColor::MovementSoft, QT_TRANSLATE_NOOP("PaletteGridWidget", "Movement at quiet emphasis")},
|
||||||
};
|
};
|
||||||
|
|
||||||
PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)
|
PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)
|
||||||
|
|
|
||||||
|
|
@ -509,6 +509,15 @@ QColor defaultAppColor(AppColor::Role role, bool dark)
|
||||||
return QColor(dark ? QStringLiteral("#ffb020") : QStringLiteral("#8a4f02"));
|
return QColor(dark ? QStringLiteral("#ffb020") : QStringLiteral("#8a4f02"));
|
||||||
case AppColor::MarkedSoft:
|
case AppColor::MarkedSoft:
|
||||||
return QColor(dark ? QStringLiteral("#c2ab7d") : QStringLiteral("#7b6a4a"));
|
return QColor(dark ? QStringLiteral("#c2ab7d") : QStringLiteral("#7b6a4a"));
|
||||||
|
// Movement is a card changing position without a gain or a loss: the
|
||||||
|
// library placements (top, bottom, X from top). Teal keeps it apart
|
||||||
|
// from the green "into play" moves and the blue informational states.
|
||||||
|
case AppColor::Movement:
|
||||||
|
return QColor(dark ? QStringLiteral("#4cc2b5") : QStringLiteral("#0e7a72"));
|
||||||
|
case AppColor::MovementStrong:
|
||||||
|
return QColor(dark ? QStringLiteral("#2dd4bf") : QStringLiteral("#096b62"));
|
||||||
|
case AppColor::MovementSoft:
|
||||||
|
return QColor(dark ? QStringLiteral("#a3c1bc") : QStringLiteral("#4a6b67"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@
|
||||||
//
|
//
|
||||||
// The icon families carry one role per emphasis level. Icon is the neutral
|
// 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,
|
// glyph ink; IconStrong and IconSoft are its primary and quiet levels. Positive,
|
||||||
// Negative, Informational, Hidden and Marked follow the same pattern, so an
|
// Negative, Informational, Hidden, Marked and Movement follow the same pattern,
|
||||||
// emphasis never has to be computed from a hue at paint time: the theme authors
|
// so an emphasis never has to be computed from a hue at paint time: the theme
|
||||||
// all three levels. Naming a variant explicitly (`IconStrong`) pins that level;
|
// authors all three levels. Naming a variant explicitly (`IconStrong`) pins that
|
||||||
// naming the family (`Icon`) lets the caller's emphasis choose.
|
// level; naming the family (`Icon`) lets the caller's emphasis choose.
|
||||||
namespace AppColor
|
namespace AppColor
|
||||||
{
|
{
|
||||||
Q_NAMESPACE
|
Q_NAMESPACE
|
||||||
|
|
@ -45,6 +45,9 @@ enum Role
|
||||||
Marked,
|
Marked,
|
||||||
MarkedStrong,
|
MarkedStrong,
|
||||||
MarkedSoft,
|
MarkedSoft,
|
||||||
|
Movement,
|
||||||
|
MovementStrong,
|
||||||
|
MovementSoft,
|
||||||
};
|
};
|
||||||
Q_ENUM_NS(Role)
|
Q_ENUM_NS(Role)
|
||||||
} // namespace AppColor
|
} // namespace AppColor
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,10 @@ AppColor::Role emphasisRole(AppColor::Role role, IconEmphasis emphasis)
|
||||||
return emphasis == IconEmphasis::Primary ? AppColor::MarkedStrong
|
return emphasis == IconEmphasis::Primary ? AppColor::MarkedStrong
|
||||||
: emphasis == IconEmphasis::Quiet ? AppColor::MarkedSoft
|
: emphasis == IconEmphasis::Quiet ? AppColor::MarkedSoft
|
||||||
: AppColor::Marked;
|
: AppColor::Marked;
|
||||||
|
case AppColor::Movement:
|
||||||
|
return emphasis == IconEmphasis::Primary ? AppColor::MovementStrong
|
||||||
|
: emphasis == IconEmphasis::Quiet ? AppColor::MovementSoft
|
||||||
|
: AppColor::Movement;
|
||||||
default:
|
default:
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -692,6 +692,9 @@ QColor ThemeManager::appColor(AppColor::Role role) const
|
||||||
case AppColor::Marked:
|
case AppColor::Marked:
|
||||||
case AppColor::MarkedStrong:
|
case AppColor::MarkedStrong:
|
||||||
case AppColor::MarkedSoft:
|
case AppColor::MarkedSoft:
|
||||||
|
case AppColor::Movement:
|
||||||
|
case AppColor::MovementStrong:
|
||||||
|
case AppColor::MovementSoft:
|
||||||
// Semantic colors are independent of the accent: a missing role must
|
// Semantic colors are independent of the accent: a missing role must
|
||||||
// not silently paint "remove" in the theme's highlight color.
|
// not silently paint "remove" in the theme's highlight color.
|
||||||
return defaultAppColor(role, isDarkModeActive());
|
return defaultAppColor(role, isDarkModeActive());
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ TEST(IconTintPreview, WritesOneSheetPerShippedTheme)
|
||||||
{"Informational", AppColor::Informational, {"arrow_right", "view", "arrow_to_top"}},
|
{"Informational", AppColor::Informational, {"arrow_right", "view", "arrow_to_top"}},
|
||||||
{"Hidden", AppColor::Hidden, {"view", "arrow_right", "swap"}},
|
{"Hidden", AppColor::Hidden, {"view", "arrow_right", "swap"}},
|
||||||
{"Marked", AppColor::Marked, {"pencil", "share", "pin"}},
|
{"Marked", AppColor::Marked, {"pencil", "share", "pin"}},
|
||||||
|
{"Movement", AppColor::Movement, {"arrow_to_top", "arrow_to_bottom", "arrow_down"}},
|
||||||
};
|
};
|
||||||
const QStringList themes = {
|
const QStringList themes = {
|
||||||
QStringLiteral("Fabric"), QStringLiteral("Fusion"), QStringLiteral("Leather"),
|
QStringLiteral("Fabric"), QStringLiteral("Fusion"), QStringLiteral("Leather"),
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ TEST(IconTreatmentPreview, WritesOneSheetPerShippedTheme)
|
||||||
{"Informational", AppColor::Informational, {"arrow_right", "view", "arrow_to_top"}},
|
{"Informational", AppColor::Informational, {"arrow_right", "view", "arrow_to_top"}},
|
||||||
{"Hidden", AppColor::Hidden, {"view", "arrow_right", "swap"}},
|
{"Hidden", AppColor::Hidden, {"view", "arrow_right", "swap"}},
|
||||||
{"Marked", AppColor::Marked, {"pencil", "share", "pin"}},
|
{"Marked", AppColor::Marked, {"pencil", "share", "pin"}},
|
||||||
|
{"Movement", AppColor::Movement, {"arrow_to_top", "arrow_to_bottom", "arrow_down"}},
|
||||||
};
|
};
|
||||||
const QStringList themes = {
|
const QStringList themes = {
|
||||||
QStringLiteral("Fabric"), QStringLiteral("Fusion"), QStringLiteral("Leather"),
|
QStringLiteral("Fabric"), QStringLiteral("Fusion"), QStringLiteral("Leather"),
|
||||||
|
|
|
||||||
|
|
@ -1541,7 +1541,8 @@ TEST_F(ThemeIconRenderTest, EmphasisSelectsTheAuthoredVariant)
|
||||||
Family{AppColor::Negative, AppColor::NegativeStrong, AppColor::NegativeSoft},
|
Family{AppColor::Negative, AppColor::NegativeStrong, AppColor::NegativeSoft},
|
||||||
Family{AppColor::Informational, AppColor::InformationalStrong, AppColor::InformationalSoft},
|
Family{AppColor::Informational, AppColor::InformationalStrong, AppColor::InformationalSoft},
|
||||||
Family{AppColor::Hidden, AppColor::HiddenStrong, AppColor::HiddenSoft},
|
Family{AppColor::Hidden, AppColor::HiddenStrong, AppColor::HiddenSoft},
|
||||||
Family{AppColor::Marked, AppColor::MarkedStrong, AppColor::MarkedSoft}}) {
|
Family{AppColor::Marked, AppColor::MarkedStrong, AppColor::MarkedSoft},
|
||||||
|
Family{AppColor::Movement, AppColor::MovementStrong, AppColor::MovementSoft}}) {
|
||||||
EXPECT_EQ(IconTint(family.base, IconEmphasis::Primary).resolve(QIcon::Normal),
|
EXPECT_EQ(IconTint(family.base, IconEmphasis::Primary).resolve(QIcon::Normal),
|
||||||
themeManager->appColor(family.strong));
|
themeManager->appColor(family.strong));
|
||||||
EXPECT_EQ(IconTint(family.base).resolve(QIcon::Normal), themeManager->appColor(family.base));
|
EXPECT_EQ(IconTint(family.base).resolve(QIcon::Normal), themeManager->appColor(family.base));
|
||||||
|
|
@ -1675,6 +1676,17 @@ TEST_F(ThemeIconRenderTest, EmphasisKeepsASemanticHue)
|
||||||
EXPECT_GT(ink.red(), ink.green()) << theme.toStdString() << static_cast<int>(role);
|
EXPECT_GT(ink.red(), ink.green()) << theme.toStdString() << static_cast<int>(role);
|
||||||
EXPECT_GT(ink.green(), ink.blue()) << theme.toStdString() << static_cast<int>(role);
|
EXPECT_GT(ink.green(), ink.blue()) << theme.toStdString() << static_cast<int>(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const AppColor::Role role : {AppColor::Movement, AppColor::MovementStrong, AppColor::MovementSoft}) {
|
||||||
|
// Movement is teal: green leads, then blue, then red.
|
||||||
|
const QColor ink = roles.value(role, defaultAppColor(role, dark));
|
||||||
|
if (!ink.isValid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++checked;
|
||||||
|
EXPECT_GT(ink.green(), ink.blue()) << theme.toStdString() << static_cast<int>(role);
|
||||||
|
EXPECT_GT(ink.blue(), ink.red()) << theme.toStdString() << static_cast<int>(role);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EXPECT_GT(checked, 0) << "no shipped theme authored a semantic emphasis level";
|
EXPECT_GT(checked, 0) << "no shipped theme authored a semantic emphasis level";
|
||||||
|
|
@ -1863,7 +1875,8 @@ TEST(IconInk, DefaultSemanticFamilyMeetsTheLadderOnPlatformSurfaces)
|
||||||
std::tuple{AppColor::Negative, AppColor::NegativeStrong, AppColor::NegativeSoft},
|
std::tuple{AppColor::Negative, AppColor::NegativeStrong, AppColor::NegativeSoft},
|
||||||
std::tuple{AppColor::Informational, AppColor::InformationalStrong, AppColor::InformationalSoft},
|
std::tuple{AppColor::Informational, AppColor::InformationalStrong, AppColor::InformationalSoft},
|
||||||
std::tuple{AppColor::Hidden, AppColor::HiddenStrong, AppColor::HiddenSoft},
|
std::tuple{AppColor::Hidden, AppColor::HiddenStrong, AppColor::HiddenSoft},
|
||||||
std::tuple{AppColor::Marked, AppColor::MarkedStrong, AppColor::MarkedSoft}}) {
|
std::tuple{AppColor::Marked, AppColor::MarkedStrong, AppColor::MarkedSoft},
|
||||||
|
std::tuple{AppColor::Movement, AppColor::MovementStrong, AppColor::MovementSoft}}) {
|
||||||
const QColor secondary = defaultAppColor(base, scheme.dark);
|
const QColor secondary = defaultAppColor(base, scheme.dark);
|
||||||
const QColor primary = defaultAppColor(strong, scheme.dark);
|
const QColor primary = defaultAppColor(strong, scheme.dark);
|
||||||
const QColor quiet = defaultAppColor(soft, scheme.dark);
|
const QColor quiet = defaultAppColor(soft, scheme.dark);
|
||||||
|
|
@ -1957,6 +1970,11 @@ TEST_F(ThemeIconRenderTest, EmphasisPreviewWritesPngsWhenRequested)
|
||||||
AppColor::InformationalStrong},
|
AppColor::InformationalStrong},
|
||||||
{"Hidden", {"view", "arrow_right", "swap"}, AppColor::HiddenSoft, AppColor::Hidden, AppColor::HiddenStrong},
|
{"Hidden", {"view", "arrow_right", "swap"}, AppColor::HiddenSoft, AppColor::Hidden, AppColor::HiddenStrong},
|
||||||
{"Marked", {"pencil", "share", "pin"}, AppColor::MarkedSoft, AppColor::Marked, AppColor::MarkedStrong},
|
{"Marked", {"pencil", "share", "pin"}, AppColor::MarkedSoft, AppColor::Marked, AppColor::MarkedStrong},
|
||||||
|
{"Movement",
|
||||||
|
{"arrow_to_top", "arrow_to_bottom", "arrow_down"},
|
||||||
|
AppColor::MovementSoft,
|
||||||
|
AppColor::Movement,
|
||||||
|
AppColor::MovementStrong},
|
||||||
};
|
};
|
||||||
const QStringList themes = {
|
const QStringList themes = {
|
||||||
QStringLiteral("Fabric"), QStringLiteral("Fusion"), QStringLiteral("Leather"),
|
QStringLiteral("Fabric"), QStringLiteral("Fusion"), QStringLiteral("Leather"),
|
||||||
|
|
@ -2157,6 +2175,11 @@ TEST_F(ThemeIconRenderTest, EmphasisTreatmentsPreviewWritesPngsWhenRequested)
|
||||||
AppColor::InformationalStrong},
|
AppColor::InformationalStrong},
|
||||||
{"Hidden", {"view", "arrow_right", "swap"}, AppColor::HiddenSoft, AppColor::Hidden, AppColor::HiddenStrong},
|
{"Hidden", {"view", "arrow_right", "swap"}, AppColor::HiddenSoft, AppColor::Hidden, AppColor::HiddenStrong},
|
||||||
{"Marked", {"pencil", "share", "pin"}, AppColor::MarkedSoft, AppColor::Marked, AppColor::MarkedStrong},
|
{"Marked", {"pencil", "share", "pin"}, AppColor::MarkedSoft, AppColor::Marked, AppColor::MarkedStrong},
|
||||||
|
{"Movement",
|
||||||
|
{"arrow_to_top", "arrow_to_bottom", "arrow_down"},
|
||||||
|
AppColor::MovementSoft,
|
||||||
|
AppColor::Movement,
|
||||||
|
AppColor::MovementStrong},
|
||||||
};
|
};
|
||||||
const QStringList themes = {
|
const QStringList themes = {
|
||||||
QStringLiteral("Fabric"), QStringLiteral("Fusion"), QStringLiteral("Leather"),
|
QStringLiteral("Fabric"), QStringLiteral("Fusion"), QStringLiteral("Leather"),
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,12 @@ TEST(ThemeAppColorTest, SemanticDefaultsFollowTheScheme)
|
||||||
EXPECT_EQ(defaultAppColor(AppColor::MarkedStrong, true), QColor(QStringLiteral("#ffb020")));
|
EXPECT_EQ(defaultAppColor(AppColor::MarkedStrong, true), QColor(QStringLiteral("#ffb020")));
|
||||||
EXPECT_EQ(defaultAppColor(AppColor::MarkedSoft, false), QColor(QStringLiteral("#7b6a4a")));
|
EXPECT_EQ(defaultAppColor(AppColor::MarkedSoft, false), QColor(QStringLiteral("#7b6a4a")));
|
||||||
EXPECT_EQ(defaultAppColor(AppColor::MarkedSoft, true), QColor(QStringLiteral("#c2ab7d")));
|
EXPECT_EQ(defaultAppColor(AppColor::MarkedSoft, true), QColor(QStringLiteral("#c2ab7d")));
|
||||||
|
EXPECT_EQ(defaultAppColor(AppColor::Movement, false), QColor(QStringLiteral("#0e7a72")));
|
||||||
|
EXPECT_EQ(defaultAppColor(AppColor::Movement, true), QColor(QStringLiteral("#4cc2b5")));
|
||||||
|
EXPECT_EQ(defaultAppColor(AppColor::MovementStrong, false), QColor(QStringLiteral("#096b62")));
|
||||||
|
EXPECT_EQ(defaultAppColor(AppColor::MovementStrong, true), QColor(QStringLiteral("#2dd4bf")));
|
||||||
|
EXPECT_EQ(defaultAppColor(AppColor::MovementSoft, false), QColor(QStringLiteral("#4a6b67")));
|
||||||
|
EXPECT_EQ(defaultAppColor(AppColor::MovementSoft, true), QColor(QStringLiteral("#a3c1bc")));
|
||||||
// Accent and neutral icon roles have no static default: an older theme
|
// Accent and neutral icon roles have no static default: an older theme
|
||||||
// written before the roles existed falls back to deriving them from its
|
// written before the roles existed falls back to deriving them from its
|
||||||
// palette rather than pinning one scheme's ink.
|
// palette rather than pinning one scheme's ink.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue