[Icons] Rewrite the icon role docs in plain language

Address review: drop the AI-cadence prose from the IconStyle/IconTint and treatment headers, use ///< for the enum member docs, and move theme_icon.cpp's anonymous namespace above the IconTint definitions. No behavior change.
This commit is contained in:
Lukas Brübach 2026-11-07 18:39:36 +01:00
parent 92b94c4ed7
commit 44ac98885a
6 changed files with 87 additions and 96 deletions

View file

@ -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);

View file

@ -28,45 +28,6 @@
#include <QtGlobal>
#include <utility>
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));

View file

@ -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.
*/

View file

@ -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

View file

@ -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
{

View file

@ -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