[Icons] Keep semantic red and green on themed glyphs

The one-glyph cutover recolored every icon from a single palette role, so a
disabled deck, a not-ready player and a remove button all lost the red or green
that carried their meaning.

Add Positive and Negative application colors beside the accents, ship
scheme-appropriate defaults for every theme, and resolve them through
ThemeManager. Icons now take an IconTint: a palette role for structural glyphs,
or a semantic role for states and add/remove actions. A disabled semantic icon
falls back to the disabled foreground; on a selected row a semantic tint keeps
its color only while it stays legible on the highlight, and a palette tint keeps
the existing WindowText -> HighlightedText swap.
This commit is contained in:
Lukas Brübach 2026-11-04 17:32:36 +01:00 • committed by Lukas Brübach
parent 9fdbe45ffb
commit 9449876a28
35 changed files with 339 additions and 53 deletions

View file

@ -6,6 +6,9 @@
#ifndef THEME_ICON_H
#define THEME_ICON_H
#include "theme_config.h"
#include <QColor>
#include <QIcon>
#include <QPalette>
#include <QPixmap>
@ -13,6 +16,41 @@
#include <QString>
#include <QStringView>
/**
* @brief Selects the color that replaces the `currentColor` sentinel.
*
* 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.
*/
class IconTint
{
public:
IconTint() = default;
IconTint(QPalette::ColorRole role); // implicit: existing palette-role callers
IconTint(AppColor::Role role); // implicit: semantic callers
/**
* @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.
*/
QColor resolve(QIcon::Mode mode) const;
private:
enum class Source
{
Palette,
AppColor,
};
Source source = Source::Palette;
QPalette::ColorRole paletteRole = QPalette::WindowText;
AppColor::Role appRole = AppColor::AccentStrong;
};
/**
* @brief Builds a palette-aware icon from a `theme:` asset.
*
@ -24,11 +62,11 @@
*
* @param prefix Asset prefix relative to the `theme:` search path, with no file
* extension (for example `icons/search`), matching themePixmap().
* @param role Palette role whose color replaces the sentinel. Selected glyphs
* use HighlightedText unless a role other than WindowText is given.
* @param tint Color that replaces the sentinel: a palette role or a semantic
* application color. Defaults to WindowText.
* @return An icon that follows the application palette.
*/
QIcon loadThemedIcon(QStringView prefix, QPalette::ColorRole role = QPalette::WindowText);
QIcon loadThemedIcon(QStringView prefix, IconTint tint = {});
/**
* @brief Renders a resolved theme SVG when it carries the `currentColor` sentinel.
@ -38,13 +76,12 @@ QIcon loadThemedIcon(QStringView prefix, QPalette::ColorRole role = QPalette::Wi
*
* @param resolvedPath Asset path relative to the `theme:` search path, as
* returned by ThemeManager::assetPath (includes extension).
* @param role Palette role whose color replaces the sentinel.
* @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.
* @return The recolored pixmap, or a null pixmap when the asset is not a
* sentinel-carrying SVG.
*/
QPixmap loadSentinelSvgPixmap(const QString &resolvedPath,
QPalette::ColorRole role = QPalette::WindowText,
const QSize &size = {});
QPixmap loadSentinelSvgPixmap(const QString &resolvedPath, IconTint tint = {}, const QSize &size = {});
#endif // THEME_ICON_H