mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-08 11:03:25 -08:00
[Icons] Paint neutral glyphs with icon ink and add an emphasis axis (#7788)
* [Icons] Paint neutral glyphs with icon ink instead of body-text ink Neutral icons resolved `currentColor` straight to `QPalette::WindowText`. A glyph is not a word: it is a 16-24px mark drawn with a 2-unit stroke, so antialiasing thins it until it reads lighter than the text beside it, in the one color the palette reserves for reading. The result is that icons recede into the chrome exactly where they should anchor it. Add `theme_icon_ink`, one rule with one job: mix a share of the theme accent into the neutral text color, then hold the result to a contrast floor. The accent share gives the glyph chroma the text does not have, so it reads as an icon rather than as leftover text, and it is derived from `QPalette::Highlight` rather than a new token. The floor keeps a theme with a weak text color from producing an illegible glyph. Only the neutral label roles (`WindowText`, `Text`) are inked. Semantic tints are hues the call site chose on purpose, disabled uses the disabled group, and the selected-row swap to `HighlightedText` is unchanged. The WCAG contrast math moves from `theme_icon_badge` to the new unit, so badge ink decisions and the floor share one implementation. Tests: the ink rule against every shipped palette, the weak-text floor, and the palette-following render tests now ask the rule instead of restating its output. A preview sheet sweeps the accent share at 0-35% over window and button surfaces for both schemes. * [Icons] Add an emphasis axis for icon ink Tint says which hue, style says how much shape, and nothing said how loud. Call sites that have a hierarchy (a card hover panel's nav strip, the visual database filter toolbar) could only pick between "the theme color" and "another theme color", so "this is the important one" had no way to be expressed and every glyph in a surface arrived with the same weight. Add `IconEmphasis` as the third axis: Quiet, Secondary, Primary. It travels inside `IconTint` instead of becoming a fourth `loadThemedIcon` parameter, so every existing call site keeps working and its look, and emphasis reads as part of the same "how should this glyph be inked" decision as the tint. - Primary lifts a neutral glyph to `QPalette::Highlight`, so the surface's own action color names the primary action instead of a second gray token, and pushes both neutral and tinted glyphs to a 7:1 floor. - Quiet blends toward the surface and stops at the 3:1 WCAG non-text floor, for glyphs that label a control rather than act (the magnifier in a search field, a section marker). - Secondary is the previous color verbatim. The default ink borrows enough accent to sit off the palette's pole (5.4:1 on light, 6.4:1 on dark), so the 7:1 primary floor is measurably above it in both schemes instead of being lost against a near-black or near-white default. The tests pin that order where it exists, and the hue shift still separates the levels at a glance. Disabled glyphs and the Selected -> HighlightedText swap are unchanged at every emphasis level. * [Icons] Render the emphasis sheet for every shipped theme The preview only rendered one theme, so a reviewer could not see that the emphasis levels behave the same way across Fabric, Fusion, Leather, Plasma and VelvetMarble. It now writes one sheet per theme and scheme: the neutral, positive and negative families at all three levels, three glyphs per family, as line, sticker and badge at 16 and 24 px, on the window and button surfaces. * Address review findings
This commit is contained in:
parent
e81ee02c24
commit
3e2884e6e1
10 changed files with 888 additions and 44 deletions
|
|
@ -3,6 +3,7 @@
|
|||
#include "libcockatrice/utility/svg_recolor.h"
|
||||
#include "theme_icon_badge.h"
|
||||
#include "theme_icon_fill.h"
|
||||
#include "theme_icon_ink.h"
|
||||
#include "theme_icon_sticker.h"
|
||||
#include "theme_manager.h"
|
||||
|
||||
|
|
@ -319,26 +320,43 @@ private:
|
|||
};
|
||||
} // namespace
|
||||
|
||||
IconTint::IconTint(QPalette::ColorRole role) : paletteRole(role)
|
||||
IconTint::IconTint(QPalette::ColorRole role, IconEmphasis _emphasis) : paletteRole(role), emphasis(_emphasis)
|
||||
{
|
||||
}
|
||||
|
||||
IconTint::IconTint(AppColor::Role role) : source(Source::AppColor), appRole(role)
|
||||
IconTint::IconTint(AppColor::Role role, IconEmphasis _emphasis)
|
||||
: source(Source::AppColor), appRole(role), emphasis(_emphasis)
|
||||
{
|
||||
}
|
||||
|
||||
IconTint IconTint::primary()
|
||||
{
|
||||
return IconTint(QPalette::WindowText, IconEmphasis::Primary);
|
||||
}
|
||||
|
||||
IconTint IconTint::quiet()
|
||||
{
|
||||
return IconTint(QPalette::WindowText, IconEmphasis::Quiet);
|
||||
}
|
||||
|
||||
QColor IconTint::resolve(QIcon::Mode mode) const
|
||||
{
|
||||
const QPalette &palette = qApp->palette();
|
||||
const QColor surface = palette.color(QPalette::Active, QPalette::Window);
|
||||
const QColor accent = palette.color(QPalette::Active, QPalette::Highlight);
|
||||
|
||||
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);
|
||||
return 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));
|
||||
// the line path, and only for the desaturated dark-scheme tones. The
|
||||
// emphasis then decides how loud that tone is, without changing its hue.
|
||||
const QColor color =
|
||||
ThemeIconInk::forEmphasis(ThemeIconFill::saturated(themeManager->appColor(appRole)), surface, emphasis);
|
||||
if (mode != QIcon::Selected) {
|
||||
return color;
|
||||
}
|
||||
|
|
@ -346,16 +364,25 @@ QColor IconTint::resolve(QIcon::Mode mode) const
|
|||
// 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
|
||||
return ThemeIconInk::contrastRatio(color, accent) >= MIN_SELECTED_CONTRAST
|
||||
? color
|
||||
: qApp->palette().color(QPalette::Active, QPalette::HighlightedText);
|
||||
: 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);
|
||||
const QColor color = palette.color(group, effective);
|
||||
// A neutral label role is inked as an icon, not as body text: it borrows a
|
||||
// little accent chroma and is held to a contrast floor. Semantic roles and
|
||||
// the selected-row swap keep the color the palette gave them.
|
||||
if (group == QPalette::Active && (effective == QPalette::WindowText || effective == QPalette::Text)) {
|
||||
// The primary action of a surface takes the accent itself; the other
|
||||
// levels stay neutral. Emphasis then sets the contrast the glyph pays.
|
||||
const QColor base = emphasis == IconEmphasis::Primary ? accent : ThemeIconInk::glyphInk(color, surface, accent);
|
||||
return ThemeIconInk::forEmphasis(base, surface, emphasis);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
QIcon loadThemedIcon(QStringView prefix, IconTint tint, IconStyle style)
|
||||
|
|
|
|||
|
|
@ -46,19 +46,47 @@ enum class IconStyle
|
|||
Sticker, ///< filled glyph + dark outline, no disc
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Selects how much attention a glyph claims inside its surface.
|
||||
*
|
||||
* Tint says which hue, style says how much shape, emphasis says how loud. The
|
||||
* three are independent: a primary action can be a line glyph in the theme
|
||||
* accent, and a quiet one can be a sticker in a dimmed tone.
|
||||
*
|
||||
* Secondary is the default and the color every icon had before the axis
|
||||
* existed, so existing call sites keep their look. Primary is for the one
|
||||
* action a surface exists for: a neutral primary glyph takes the accent, and a
|
||||
* tinted one is pushed to full contrast. Quiet is for glyphs that label rather
|
||||
* than act, such as the magnifier inside a search field: legible, but never
|
||||
* competing with the buttons next to it.
|
||||
*/
|
||||
enum class IconEmphasis
|
||||
{
|
||||
Quiet, // labels rather than acts
|
||||
Secondary, // the default action weight
|
||||
Primary, // the action the surface exists for
|
||||
};
|
||||
|
||||
/**
|
||||
* @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 choose the role. The active theme supplies the value.
|
||||
* Callers pick the role by intent; the theme owns the concrete value. Emphasis
|
||||
* is part of the same decision: both answer how the glyph is inked.
|
||||
*/
|
||||
class IconTint
|
||||
{
|
||||
public:
|
||||
IconTint() = default;
|
||||
IconTint(QPalette::ColorRole role); // implicit: existing palette-role callers
|
||||
IconTint(AppColor::Role role); // implicit: semantic callers
|
||||
IconTint(QPalette::ColorRole role, IconEmphasis emphasis = IconEmphasis::Secondary); // implicit for role callers
|
||||
IconTint(AppColor::Role role, IconEmphasis emphasis = IconEmphasis::Secondary); // implicit for semantic callers
|
||||
|
||||
/** @brief A neutral glyph in the accent tone, for the surface's primary action. */
|
||||
static IconTint primary();
|
||||
|
||||
/** @brief A dimmed neutral glyph, for a control that labels rather than acts. */
|
||||
static IconTint quiet();
|
||||
|
||||
/**
|
||||
* @brief Resolves the concrete color for an icon mode.
|
||||
|
|
@ -68,6 +96,16 @@ public:
|
|||
* 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.
|
||||
*
|
||||
* Neutral label roles (WindowText, Text) resolve to icon ink rather than the
|
||||
* raw palette color: ThemeIconInk::glyphInk borrows a little accent chroma
|
||||
* and holds the glyph to a contrast floor, because a 2px glyph in body-text
|
||||
* ink reads thinner than the text beside it. Semantic roles keep the hue the
|
||||
* call site asked for.
|
||||
*
|
||||
* Emphasis is applied after the hue: Primary lifts a neutral glyph to the
|
||||
* accent and pushes a tinted one to full contrast; Quiet blends either
|
||||
* toward the surface down to the non-text legibility floor.
|
||||
*/
|
||||
QColor resolve(QIcon::Mode mode) const;
|
||||
|
||||
|
|
@ -81,6 +119,7 @@ private:
|
|||
Source source = Source::Palette;
|
||||
QPalette::ColorRole paletteRole = QPalette::WindowText;
|
||||
AppColor::Role appRole = AppColor::AccentStrong;
|
||||
IconEmphasis emphasis = IconEmphasis::Secondary;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
#include <QRectF>
|
||||
#include <QSvgRenderer>
|
||||
#include <QtGlobal>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
|
@ -47,22 +46,6 @@ DiscFill discFill(const QColor &tone)
|
|||
|
||||
namespace ThemeIconBadge
|
||||
{
|
||||
double relativeLuminance(const QColor &color)
|
||||
{
|
||||
const auto linear = [](double channel) {
|
||||
channel /= 255.0;
|
||||
return channel <= 0.03928 ? channel / 12.92 : std::pow((channel + 0.055) / 1.055, 2.4);
|
||||
};
|
||||
return 0.2126 * linear(color.red()) + 0.7152 * linear(color.green()) + 0.0722 * linear(color.blue());
|
||||
}
|
||||
|
||||
double contrastRatio(const QColor &first, const QColor &second)
|
||||
{
|
||||
const double a = relativeLuminance(first);
|
||||
const double b = relativeLuminance(second);
|
||||
return (qMax(a, b) + 0.05) / (qMin(a, b) + 0.05);
|
||||
}
|
||||
|
||||
QColor glyphColor(const QColor &tone)
|
||||
{
|
||||
// The badge carries the ink the pre-sticker badge did: white on the light
|
||||
|
|
|
|||
|
|
@ -31,16 +31,6 @@
|
|||
*/
|
||||
namespace ThemeIconBadge
|
||||
{
|
||||
/**
|
||||
* @brief WCAG relative luminance, used to test a color against a background.
|
||||
*
|
||||
* Used by IconTint::resolve()'s selected-row contrast test.
|
||||
*/
|
||||
double relativeLuminance(const QColor &color);
|
||||
|
||||
/** @brief WCAG contrast ratio between two opaque colors, from 1.0 to 21.0. */
|
||||
double contrastRatio(const QColor &first, const QColor &second);
|
||||
|
||||
/**
|
||||
* @brief Glyph color for a badge disc.
|
||||
*
|
||||
|
|
|
|||
135
cockatrice/src/interface/theme_icon_ink.cpp
Normal file
135
cockatrice/src/interface/theme_icon_ink.cpp
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#include "theme_icon_ink.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
// How much of the theme accent a neutral glyph borrows. A quarter still reads as
|
||||
// near-black at 16-24px, which is what made the first cut look unchanged, so the
|
||||
// glyph takes most of the accent's hue. It stops short of the accent itself: the
|
||||
// glyph keeps its own contrast against the surface instead of becoming a second
|
||||
// accent fill. Every shipped theme accents in blue or violet, so the borrowed hue
|
||||
// stays neutral chrome instead of implying a state.
|
||||
constexpr double ACCENT_MIX = 0.70;
|
||||
// An icon that labels a control is carrying text; see ui-assets.md.
|
||||
constexpr double LABEL_CONTRAST_FLOOR = 4.5;
|
||||
// A primary glyph has to out-read the default one on both schemes. The accent is
|
||||
// a mid-tone: on a light surface it is *lighter* than the neutral ink, and on a
|
||||
// dark surface it is *darker*, so neither "the accent as-is" nor "a contrast
|
||||
// floor shared with the default" keeps the primary above the secondary. A 7:1
|
||||
// target does: it darkens the accent on light schemes and lightens it on dark
|
||||
// ones, so the primary level always out-contrasts the default level.
|
||||
constexpr double PRIMARY_CONTRAST = 7.0;
|
||||
// A quiet glyph is blended this far into its surface. It has to stay findable
|
||||
// without competing with the actions beside it.
|
||||
constexpr double QUIET_BLEND = 0.55;
|
||||
// WCAG 2.2 SC 1.4.11, the floor for a non-text element that carries meaning.
|
||||
constexpr double QUIET_CONTRAST_FLOOR = 3.0;
|
||||
// Binary-search depth for reachContrast(). 24 halvings resolve to well under
|
||||
// one 8-bit channel step, so the result is stable across platforms.
|
||||
constexpr int CONTRAST_SEARCH_STEPS = 24;
|
||||
} // namespace
|
||||
|
||||
namespace ThemeIconInk
|
||||
{
|
||||
double relativeLuminance(const QColor &color)
|
||||
{
|
||||
const auto linear = [](double channel) {
|
||||
channel /= 255.0;
|
||||
return channel <= 0.03928 ? channel / 12.92 : std::pow((channel + 0.055) / 1.055, 2.4);
|
||||
};
|
||||
return 0.2126 * linear(color.red()) + 0.7152 * linear(color.green()) + 0.0722 * linear(color.blue());
|
||||
}
|
||||
|
||||
double contrastRatio(const QColor &first, const QColor &second)
|
||||
{
|
||||
const double a = relativeLuminance(first);
|
||||
const double b = relativeLuminance(second);
|
||||
return (qMax(a, b) + 0.05) / (qMin(a, b) + 0.05);
|
||||
}
|
||||
|
||||
QColor blend(const QColor &color, const QColor &target, double amount)
|
||||
{
|
||||
const double clamped = qBound(0.0, amount, 1.0);
|
||||
const auto channel = [clamped](int from, int to) { return qRound(from + (to - from) * clamped); };
|
||||
return QColor(channel(color.red(), target.red()), channel(color.green(), target.green()),
|
||||
channel(color.blue(), target.blue()));
|
||||
}
|
||||
|
||||
QColor reachContrast(const QColor &color, const QColor &background, double minRatio)
|
||||
{
|
||||
if (contrastRatio(color, background) >= minRatio) {
|
||||
return color;
|
||||
}
|
||||
// Move away from the background: toward whichever pole actually reaches
|
||||
// further. Black and white cross at a luminance of about 0.18, not 0.5, so
|
||||
// the midpoint test would pick white on a surface where black is far more
|
||||
// legible -- and could drag an ink that already cleared the floor to white.
|
||||
// The hue is kept by interpolating, not by replacing.
|
||||
const QColor black(Qt::black);
|
||||
const QColor white(Qt::white);
|
||||
const QColor pole = contrastRatio(black, background) >= contrastRatio(white, background) ? black : white;
|
||||
if (contrastRatio(pole, background) < minRatio) {
|
||||
return pole;
|
||||
}
|
||||
|
||||
// The ratio is monotone along the path from color to pole, so a bisection
|
||||
// finds the first point that clears the floor. Stepping the opposite way
|
||||
// from the pole keeps the glyph as close to its palette color as possible.
|
||||
double low = 0.0;
|
||||
double high = 1.0;
|
||||
for (int step = 0; step < CONTRAST_SEARCH_STEPS; ++step) {
|
||||
const double middle = (low + high) / 2.0;
|
||||
if (contrastRatio(blend(color, pole, middle), background) >= minRatio) {
|
||||
high = middle;
|
||||
} else {
|
||||
low = middle;
|
||||
}
|
||||
}
|
||||
return blend(color, pole, high);
|
||||
}
|
||||
|
||||
QColor glyphInk(const QColor &text, const QColor &surface, const QColor &accent, double accentMix)
|
||||
{
|
||||
return reachContrast(blend(text, accent, accentMix), surface, LABEL_CONTRAST_FLOOR).toRgb();
|
||||
}
|
||||
|
||||
double defaultAccentMix()
|
||||
{
|
||||
return ACCENT_MIX;
|
||||
}
|
||||
|
||||
double labelContrastFloor()
|
||||
{
|
||||
return LABEL_CONTRAST_FLOOR;
|
||||
}
|
||||
|
||||
QColor forEmphasis(const QColor &color, const QColor &surface, IconEmphasis emphasis)
|
||||
{
|
||||
switch (emphasis) {
|
||||
case IconEmphasis::Primary:
|
||||
return reachContrast(color, surface, PRIMARY_CONTRAST).toRgb();
|
||||
case IconEmphasis::Quiet:
|
||||
return reachContrast(blend(color, surface, QUIET_BLEND), surface, QUIET_CONTRAST_FLOOR).toRgb();
|
||||
case IconEmphasis::Secondary:
|
||||
break;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
double primaryContrastFloor()
|
||||
{
|
||||
return PRIMARY_CONTRAST;
|
||||
}
|
||||
|
||||
double quietBlend()
|
||||
{
|
||||
return QUIET_BLEND;
|
||||
}
|
||||
|
||||
double quietContrastFloor()
|
||||
{
|
||||
return QUIET_CONTRAST_FLOOR;
|
||||
}
|
||||
} // namespace ThemeIconInk
|
||||
127
cockatrice/src/interface/theme_icon_ink.h
Normal file
127
cockatrice/src/interface/theme_icon_ink.h
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* @file theme_icon_ink.h
|
||||
* @ingroup Interface
|
||||
*/
|
||||
|
||||
#ifndef THEME_ICON_INK_H
|
||||
#define THEME_ICON_INK_H
|
||||
|
||||
#include "theme_icon.h"
|
||||
|
||||
#include <QColor>
|
||||
|
||||
/**
|
||||
* @brief Turns a text color into the ink a glyph is painted with.
|
||||
*
|
||||
* A glyph is not a word. Body text is read in sentences, at a size where a 2px
|
||||
* stroke would be unusually heavy; a glyph is a 16px mark of the same stroke,
|
||||
* and antialiasing eats its edges, so the same color that reads as solid type
|
||||
* reads as a thin gray line beside it. Painting icons with `WindowText` verbatim
|
||||
* is what makes them recede.
|
||||
*
|
||||
* The rule here is deliberately small: give the glyph a little of the theme's
|
||||
* accent chroma, then hold it to a contrast floor against the surface it sits
|
||||
* on. The chroma is what makes it read as an icon rather than as leftover text;
|
||||
* the floor keeps a theme with a weak text color from producing a glyph that is
|
||||
* not legible. Neither step invents a color: both are derived from the palette
|
||||
* the theme already ships.
|
||||
*
|
||||
* The rule only applies to the neutral label roles. A semantic tint
|
||||
* (`AppColor::Positive`, `QPalette::Highlight`, `QPalette::Link`) is a hue the
|
||||
* call site chose on purpose, so it is left alone.
|
||||
*/
|
||||
namespace ThemeIconInk
|
||||
{
|
||||
/**
|
||||
* @brief WCAG relative luminance of an opaque color.
|
||||
*/
|
||||
double relativeLuminance(const QColor &color);
|
||||
|
||||
/**
|
||||
* @brief WCAG contrast ratio between two opaque colors, from 1.0 to 21.0.
|
||||
*/
|
||||
double contrastRatio(const QColor &first, const QColor &second);
|
||||
|
||||
/**
|
||||
* @brief Mixes two colors, @p amount = 0 returning @p color and 1 returning @p target.
|
||||
*
|
||||
* RGB interpolation, with no gamma or hue-space correction: the inputs are
|
||||
* palette colors that already sit in sRGB, and the result has to be predictable
|
||||
* enough for a theme author to reason about it.
|
||||
*/
|
||||
QColor blend(const QColor &color, const QColor &target, double amount);
|
||||
|
||||
/**
|
||||
* @brief Pushes @p color away from @p background until it reaches @p minRatio.
|
||||
*
|
||||
* Moves @p color toward black on a light background and toward white on a dark
|
||||
* one, so the hue survives the adjustment. Returns @p color unchanged when it
|
||||
* already meets the floor, which is the common case for a shipped palette.
|
||||
*
|
||||
* @param color Color to adjust.
|
||||
* @param background Surface the color is drawn on.
|
||||
* @param minRatio Contrast floor, as a WCAG ratio.
|
||||
* @return The adjusted color; the nearer pole when even that cannot reach it.
|
||||
*/
|
||||
QColor reachContrast(const QColor &color, const QColor &background, double minRatio);
|
||||
|
||||
/**
|
||||
* @brief Accent share used by production callers of glyphInk().
|
||||
*
|
||||
* Exposed so the preview sheet and the tests name the value they pin instead of
|
||||
* repeating the literal, and so the default argument below has exactly one
|
||||
* source: the constant in theme_icon_ink.cpp.
|
||||
*/
|
||||
double defaultAccentMix();
|
||||
|
||||
/**
|
||||
* @brief Icon ink for a neutral glyph, given the palette it is drawn from.
|
||||
*
|
||||
* @param text Neutral text color, normally `QPalette::WindowText`.
|
||||
* @param surface Surface the glyph is drawn on, normally `QPalette::Window`.
|
||||
* @param accent The theme accent, normally `QPalette::Highlight`.
|
||||
* @param accentMix How much of the accent color to borrow. Defaults to
|
||||
* defaultAccentMix(); previews and tests sweep it to compare
|
||||
* strengths, production callers do not pass it.
|
||||
* @return The ink to resolve `currentColor` to, as an RGB color.
|
||||
*/
|
||||
QColor glyphInk(const QColor &text, const QColor &surface, const QColor &accent, double accentMix = defaultAccentMix());
|
||||
|
||||
/**
|
||||
* @brief Contrast floor a glyph that labels a control has to clear.
|
||||
*
|
||||
* 4.5:1, the Text-level WCAG ratio: an icon that is the only label for a button
|
||||
* is carrying text, and the icon guidelines in `.opencode/docs/ui-assets.md`
|
||||
* already ask for that when there is no visible label.
|
||||
*/
|
||||
double labelContrastFloor();
|
||||
|
||||
/**
|
||||
* @brief Applies an emphasis level to an already-hued glyph color.
|
||||
*
|
||||
* Secondary keeps the hue untouched, so it is the no-op the default needs.
|
||||
* Primary pushes the color to a higher floor than the default, because the
|
||||
* accent is a mid-tone: on a light scheme it is lighter than the neutral ink and
|
||||
* on a dark one darker, so only a stronger contrast target keeps the primary
|
||||
* level above the secondary on both. Quiet
|
||||
* blends the color toward the surface it sits on and stops at the non-text
|
||||
* legibility floor, so a labelling glyph recedes without disappearing.
|
||||
*
|
||||
* @param color Hue-resolved glyph color.
|
||||
* @param surface Surface the glyph is drawn on.
|
||||
* @param emphasis How much attention the glyph claims.
|
||||
* @return The emphasized color, as an RGB color.
|
||||
*/
|
||||
QColor forEmphasis(const QColor &color, const QColor &surface, IconEmphasis emphasis);
|
||||
|
||||
/** @brief Contrast floor the primary level reaches, so it out-reads the default. */
|
||||
double primaryContrastFloor();
|
||||
|
||||
/** @brief How far a quiet glyph is blended into its surface. */
|
||||
double quietBlend();
|
||||
|
||||
/** @brief Contrast floor a quiet glyph keeps, the WCAG non-text minimum. */
|
||||
double quietContrastFloor();
|
||||
} // namespace ThemeIconInk
|
||||
|
||||
#endif // THEME_ICON_INK_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue