diff --git a/cockatrice/src/interface/theme_config.h b/cockatrice/src/interface/theme_config.h index 6f68fa7cb..7d05e4bf0 100644 --- a/cockatrice/src/interface/theme_config.h +++ b/cockatrice/src/interface/theme_config.h @@ -48,8 +48,8 @@ Q_ENUM_NS(Role) * define one. * * Accent roles return an invalid QColor because ThemeManager derives them from - * the palette highlight. Semantic roles return an opaque, scheme-appropriate - * default so a theme written before the role existed still renders it sensibly. + * the palette highlight. Semantic roles return an opaque default that suits the + * scheme, so themes written before the role existed still render it. */ QColor defaultAppColor(AppColor::Role role, bool dark); diff --git a/cockatrice/src/interface/theme_icon.cpp b/cockatrice/src/interface/theme_icon.cpp index 9c04929ba..be3adb9b5 100644 --- a/cockatrice/src/interface/theme_icon.cpp +++ b/cockatrice/src/interface/theme_icon.cpp @@ -28,45 +28,6 @@ #include #include -IconTint::IconTint(QPalette::ColorRole role) : paletteRole(role) -{ -} - -IconTint::IconTint(AppColor::Role role) : source(Source::AppColor), appRole(role) -{ -} - -QColor IconTint::resolve(QIcon::Mode mode) const -{ - if (source == Source::AppColor) { - // A disabled control must not keep signalling its semantic color. - if (mode == QIcon::Disabled) { - return qApp->palette().color(QPalette::Disabled, QPalette::WindowText); - } - // The three treatments paint the same tone: a line glyph, a badge disc - // and a sticker fill must not disagree on how saturated the role is. - // The fill treatments apply this floor themselves, so this only changes - // the line path, and only for the desaturated dark-scheme tones. - const QColor color = ThemeIconFill::saturated(themeManager->appColor(appRole)); - if (mode != QIcon::Selected) { - return color; - } - // Selected rows paint their icon on the highlight colour. Keep the - // semantic tint when it stays legible there; otherwise the glyph would - // vanish into the selection. - constexpr double MIN_SELECTED_CONTRAST = 3.0; - const QColor highlight = qApp->palette().color(QPalette::Active, QPalette::Highlight); - return ThemeIconBadge::contrastRatio(color, highlight) >= MIN_SELECTED_CONTRAST - ? color - : qApp->palette().color(QPalette::Active, QPalette::HighlightedText); - } - - const QPalette::ColorGroup group = mode == QIcon::Disabled ? QPalette::Disabled : QPalette::Active; - const QPalette::ColorRole effective = - mode == QIcon::Selected && paletteRole == QPalette::WindowText ? QPalette::HighlightedText : paletteRole; - return qApp->palette().color(group, effective); -} - namespace { /** @@ -354,6 +315,45 @@ private: }; } // namespace +IconTint::IconTint(QPalette::ColorRole role) : paletteRole(role) +{ +} + +IconTint::IconTint(AppColor::Role role) : source(Source::AppColor), appRole(role) +{ +} + +QColor IconTint::resolve(QIcon::Mode mode) const +{ + if (source == Source::AppColor) { + // A disabled control must not keep signalling its semantic color. + if (mode == QIcon::Disabled) { + return qApp->palette().color(QPalette::Disabled, QPalette::WindowText); + } + // The three treatments paint the same tone: a line glyph, a badge disc + // and a sticker fill must not disagree on how saturated the role is. + // The fill treatments apply this floor themselves, so this only changes + // the line path, and only for the desaturated dark-scheme tones. + const QColor color = ThemeIconFill::saturated(themeManager->appColor(appRole)); + if (mode != QIcon::Selected) { + return color; + } + // Selected rows paint their icon on the highlight colour. Keep the + // semantic tint when it stays legible there; otherwise the glyph would + // vanish into the selection. + constexpr double MIN_SELECTED_CONTRAST = 3.0; + const QColor highlight = qApp->palette().color(QPalette::Active, QPalette::Highlight); + return ThemeIconBadge::contrastRatio(color, highlight) >= MIN_SELECTED_CONTRAST + ? color + : qApp->palette().color(QPalette::Active, QPalette::HighlightedText); + } + + const QPalette::ColorGroup group = mode == QIcon::Disabled ? QPalette::Disabled : QPalette::Active; + const QPalette::ColorRole effective = + mode == QIcon::Selected && paletteRole == QPalette::WindowText ? QPalette::HighlightedText : paletteRole; + return qApp->palette().color(group, effective); +} + QIcon loadThemedIcon(QStringView prefix, IconTint tint, IconStyle style) { return QIcon(new ThemeSvgIconEngine(prefix.toString(), tint, style)); diff --git a/cockatrice/src/interface/theme_icon.h b/cockatrice/src/interface/theme_icon.h index aad38c7cb..e1a867fc0 100644 --- a/cockatrice/src/interface/theme_icon.h +++ b/cockatrice/src/interface/theme_icon.h @@ -19,22 +19,21 @@ /** * @brief Selects how a `currentColor` glyph is painted. * - * Style is a treatment, not a color: the tint still decides the tone, and the - * style decides how much shape carries that tone. + * The tint decides the tone. The style decides how much shape carries it. * - * Line is the default and the behavior every caller had before the axis - * existed: the recolored glyph alone, at the author's stroke weight. It stays - * the right choice for navigation, menu and toolbar glyphs, where a row of - * badges would outshout the content. + * Line is the default and matches what every caller did before this axis + * existed: the recolored glyph alone, at the author's stroke weight. Use it for + * navigation, menu and toolbar glyphs, where a row of badges would compete with + * the content. * * Badge reproduces the pre-#7595 ready/not-ready art: a filled disc, a dark ring - * and a contrast glyph derived from the tone. It costs attention as a filled - * shape, so it is opt-in per call site and it is not a generic semantic - * treatment: the add/remove/delete icons were filled glyphs with an outline and - * no disc, so badging them would invent a look the UI never had. Authored art - * that carries its own colors is never badged; it falls back to the asset path. - * Disabled icons also fall back to the line glyph, because a filled disc at full - * mass does not read as unavailable. + * and a contrast glyph derived from the tone. A filled shape draws more + * attention than a line, so it is opt-in per call site. It was made for the + * ready/not-ready icons. The add/remove/delete icons were filled glyphs with an + * outline and no disc, so badging them would produce art the UI never had. + * Authored art that carries its own colors is never badged and falls back to the + * asset path. Disabled icons fall back to the line glyph too, because a filled + * disc at full mass does not read as unavailable. * * Sticker reproduces the other half of the pre-#7595 set: the filled add, remove * and delete glyphs, painted with a tone gradient and a dark outline and no @@ -42,9 +41,9 @@ */ enum class IconStyle { - Line, // the recolored glyph alone - Badge, // filled disc + dark ring + contrast glyph - Sticker, // filled glyph + dark outline, no disc + Line, ///< the recolored glyph alone + Badge, ///< filled disc + dark ring + contrast glyph + Sticker, ///< filled glyph + dark outline, no disc }; /** @@ -52,7 +51,7 @@ enum class IconStyle * * A palette role follows Qt's structural palette. An application color carries * meaning that QPalette cannot express, such as positive and negative states. - * Callers pick the role by intent; the theme owns the concrete value. + * Callers choose the role. The active theme supplies the value. */ class IconTint { @@ -64,12 +63,11 @@ public: /** * @brief Resolves the concrete color for an icon mode. * - * Disabled application colors resolve to the disabled foreground so an - * unavailable control does not keep signalling its semantic color. Selected - * palette colors keep the existing WindowText -> HighlightedText swap. - * Application colors pass through the filled treatments' saturation floor, - * so a line glyph, a badge disc and a sticker fill carry the same tone - * instead of a pale line beside a saturated shape. + * A disabled application color resolves to the disabled foreground, so an + * unavailable control does not keep its semantic color. A selected palette + * color keeps the WindowText -> HighlightedText swap. Application colors go + * through the saturation floor shared by the filled treatments, so a line + * glyph, a badge disc and a sticker fill carry the same tone. */ QColor resolve(QIcon::Mode mode) const; @@ -98,8 +96,7 @@ private: * extension (for example `icons/search`), matching themePixmap(). * @param tint Color that replaces the sentinel: a palette role or a semantic * application color. Defaults to WindowText. - * @param style Treatment of the glyph: a line, or one of the filled treatments - * that keep the pre-migration art reading. Defaults to Line. + * @param style Treatment of the glyph: Line, Badge or Sticker. Defaults to Line. * @return An icon that follows the application palette. */ QIcon loadThemedIcon(QStringView prefix, IconTint tint = {}, IconStyle style = IconStyle::Line); @@ -115,9 +112,8 @@ QIcon loadThemedIcon(QStringView prefix, IconTint tint = {}, IconStyle style = I * @param tint Color that replaces the sentinel: a palette role or a semantic * application color. Defaults to WindowText. * @param size Pixel size to render at; an empty size uses the SVG's intrinsic size. - * @param style Treatment of the glyph: a line, or one of the filled treatments. - * A treatment on a non-sentinel asset stays null, like the line - * path. + * @param style Treatment of the glyph: Line, Badge or Sticker. A treatment on a + * non-sentinel asset stays null, like the line path. * @return The recolored pixmap, or a null pixmap when the asset is not a * sentinel-carrying SVG. */ diff --git a/cockatrice/src/interface/theme_icon_badge.h b/cockatrice/src/interface/theme_icon_badge.h index 5ff839034..cdb43cbd9 100644 --- a/cockatrice/src/interface/theme_icon_badge.h +++ b/cockatrice/src/interface/theme_icon_badge.h @@ -14,19 +14,16 @@ /** * @brief Paints the badge treatment of a `currentColor` glyph. * - * A tint alone can say "positive" or "negative", but a 2px line at 16px reads as - * a different thing from the filled badge art the UI used before the one-glyph - * cutover. The badge treatment keeps that shape: a filled disc, a dark ring, and - * a contrast glyph, all derived from the one tone the tint resolves to. So the - * theme still owns the color, and callers only choose between a line and a - * badge. + * A tint alone changes the color of a 2px line, but the UI drew ready and + * not-ready states as a filled badge before the one-glyph cutover. The badge + * treatment keeps that shape: a filled disc, a dark ring and a contrast glyph, + * all derived from the tone the tint resolves to. The theme still owns the + * color. Callers only choose between a line and a badge. * - * The trade-off is fidelity against contrast. A badge is a small picture: it - * survives a busy toolbar and a tinted row better than a hairline, and it can - * carry a light or dark glyph independent of the surrounding text. In exchange - * it costs more pixels of attention per control, and its glyph color follows the - * scheme it is drawn for rather than the window's text color, so it is - * deliberately not the palette's foreground. + * A badge survives a busy toolbar and a tinted row better than a hairline, and + * its glyph can be light or dark regardless of the surrounding text. The cost is + * more attention per control, and the glyph color follows the scheme it is drawn + * for instead of the window's text color. * * Everything here works in device pixels. The icon engine renders at the * device-pixel size it already computed and sets the pixmap's device pixel ratio diff --git a/cockatrice/src/interface/theme_icon_fill.h b/cockatrice/src/interface/theme_icon_fill.h index 16e2f09f2..4efcc136e 100644 --- a/cockatrice/src/interface/theme_icon_fill.h +++ b/cockatrice/src/interface/theme_icon_fill.h @@ -11,16 +11,15 @@ /** * @brief Picks the two colors of a filled treatment's vertical gradient. * - * The pre-#7595 icons were filled with a lighter top over a darker bottom, and - * the semantic tones are tuned for text rather than for filling a whole shape: - * the dark-scheme tones especially are deliberately desaturated, which reads - * washed out once a disc or a glyph carries them. The badge and the sticker - * therefore share one fill rule, so the two sit next to each other with the same - * green or red. + * The pre-#7595 icons were filled with a lighter top over a darker bottom. The + * semantic tones are tuned for text, not for filling a whole shape; the + * dark-scheme tones in particular are desaturated and read washed out once a + * disc or a glyph carries them. The badge and the sticker share one fill rule so + * they sit next to each other with the same green or red. * - * The rule needs no scheme switch of its own: a tone whose value is already high - * is a dark-scheme tone, and its gradient is pulled down instead of up, because - * filling a shape with a tone that light is what reads pale. + * The rule needs no scheme switch: a tone whose value is already high is a + * dark-scheme tone, so its gradient is pulled down. Filling a shape with a tone + * that light is what reads pale. */ namespace ThemeIconFill { diff --git a/cockatrice/src/interface/theme_icon_sticker.h b/cockatrice/src/interface/theme_icon_sticker.h index a2ad51bb7..d9e5ec3ba 100644 --- a/cockatrice/src/interface/theme_icon_sticker.h +++ b/cockatrice/src/interface/theme_icon_sticker.h @@ -15,15 +15,14 @@ * @brief Paints the sticker treatment of a `currentColor` glyph. * * The pre-#7595 add, remove and delete icons were filled shapes with a black - * outline, not discs. A tint alone cannot bring that back: it produces a - * hairline, and a badge disc would be a different icon language. The sticker + * outline and no disc. A tint alone cannot reproduce that: it produces a + * hairline, and a badge disc is a different icon language. The sticker * treatment rebuilds the look from the same sentinel art by drawing the glyph * twice, once with a dark outline and once with a vertical tone gradient on top. * - * The trade-off is weight against crispness. A sticker is louder than a line, - * which is what an add or a destructive action wants, but it needs a size where - * the tone band is at least two device pixels wide. Callers opt in; navigation - * and menus keep the line treatment. + * A sticker is louder than a line, which suits an add or a destructive action. + * It needs a size where the tone band is at least two device pixels wide. + * Callers opt in. Navigation and menus keep the line treatment. * * Everything here works in device pixels, like the badge treatment: the engine * renders at the size it already computed and attaches the device pixel ratio