120 lines
5.8 KiB
C++
120 lines
5.8 KiB
C++
#pragma once
|
|
// theme — the palette + type-scale core of the shared drawing kit: a ROLE-based color model
|
|
// (bg/base, bg/panel, bg/cell, line/hairline, text/primary, text/dim, accent/primary,
|
|
// accent/secondary, accent/tertiary, accent/hot, overlay/trace, warn), an interaction-state
|
|
// model (rest/hover/active/pressed/dragging/focus/disabled), and the WCAG contrast math that
|
|
// lets a unit test prove every text-on-surface pair clears its floor.
|
|
//
|
|
// Every role color is produced by roleColor() from ONE direction constants block (theme.cpp) —
|
|
// the single point of change; no shell hardcodes a color, it asks by role. The spectral hue ramp
|
|
// (spectralColor) lives here too so the keyboard strip derives its per-note hue from the same
|
|
// source.
|
|
|
|
#include <cstdint>
|
|
|
|
namespace reasampler::ui {
|
|
|
|
// Straight 8-bit-per-channel RGBA, LICE-free; draw_kit converts to LICE_pixel at the boundary.
|
|
// Named "KitColor" (not "Color"/"RGBA") to avoid collision.
|
|
struct KitColor {
|
|
std::uint8_t r = 0;
|
|
std::uint8_t g = 0;
|
|
std::uint8_t b = 0;
|
|
std::uint8_t a = 255;
|
|
|
|
bool operator==(const KitColor& o) const {
|
|
return r == o.r && g == o.g && b == o.b && a == o.a;
|
|
}
|
|
};
|
|
|
|
// Structural palette roles, direction-independent — the shell always asks by role.
|
|
enum class Role {
|
|
BgBase, // window canvas
|
|
BgPanel, // a raised region (list, waveform pane)
|
|
BgCell, // a control / row surface
|
|
LineHairline, // separators (used sparingly — elevation carries most separation)
|
|
TextPrimary, // labels, values
|
|
TextDim, // secondary / units
|
|
AccentPrimary, // live / active / selected — where the punch lives (pastel lime)
|
|
AccentSecondary,// categorical role A (deep teal) — a distinct KIND, never intensity
|
|
AccentTertiary,// categorical role B (pastel purple) — a distinct KIND, never intensity
|
|
AccentHot, // hover / live / drag feedback (a brighter tint OF the primary accent)
|
|
OverlayTrace, // a line/handle traced OVER an accent fill — reads against the fill AND the
|
|
// surface behind it, which no accent role can do (they are all too light)
|
|
Warn, // clip / destructive (prune, delete) — reserved for byte-deleting states
|
|
};
|
|
|
|
// Interaction-state model every kit component honors; stateShift (roleColorState) is the
|
|
// role-surface transform for the current state.
|
|
enum class InteractionState {
|
|
Rest,
|
|
Hover,
|
|
Active,
|
|
Pressed,
|
|
Dragging,
|
|
Focus,
|
|
Disabled,
|
|
};
|
|
|
|
// WCAG 2.1's large-scale thresholds, in px at 96dpi (18pt = 24px normal, 14pt = 18.66px bold).
|
|
// A previous revision wrote the BOLD threshold as the normal one and both ~25% low, which let
|
|
// 15px semibold self-classify as Large and clear a floor it was not entitled to — draw sites
|
|
// that want the Large floor static_assert their font against these.
|
|
inline constexpr double kLargeTextMinPx = 24.0;
|
|
inline constexpr double kLargeTextMinBoldPx = 18.66;
|
|
|
|
// Text size classes for the WCAG floor: "Large" (>= 24px, or >= ~18.66px BOLD) and UI-state
|
|
// indicators clear at 3:1; body text clears at 4.5:1 (WCAG 2.1 AA).
|
|
enum class TextClass {
|
|
Body, // AA 4.5:1
|
|
Large, // AA-large 3:1 (also the floor for state indicators)
|
|
};
|
|
|
|
// The concrete color for a role, from the one direction constants block — the single choke
|
|
// point re-picking the direction touches.
|
|
KitColor roleColor(Role role);
|
|
|
|
// roleColor(role) transformed by state (hover lightens toward accent/hot, pressed darkens,
|
|
// disabled desaturates + drops alpha, etc). Rest returns roleColor(role) unchanged.
|
|
KitColor roleColorState(Role role, InteractionState state);
|
|
|
|
// Spectral hue ramp for the keyboard strip: maps normalized position t in [0, 1] (low note ->
|
|
// high note) through a pastel lime -> teal -> purple arc, so the strip reads as part of the
|
|
// palette rather than a separate flourish. The MID stop is its own value; lo/hi remain aliases
|
|
// of accent/primary and accent/tertiary, so a categorical accent move can still reorder the
|
|
// ramp — testSpectralRampLuminanceIsMonotonic is the build-time catch, not the structure.
|
|
// t is clamped to [0, 1].
|
|
KitColor spectralColor(double t);
|
|
|
|
// --- WCAG contrast (the "punch" rule, made testable) --------------------------
|
|
|
|
// The effective opaque color of `over` drawn at `alpha` on top of `under` — the composition the
|
|
// contrast math needs before it can judge a translucent fill. alpha is clamped to [0, 1]; the
|
|
// result carries `under`'s alpha byte.
|
|
KitColor compositeOver(const KitColor& over, const KitColor& under, double alpha);
|
|
|
|
// The waveform's loop-span fill alpha. Named HERE rather than at its draw site so the contrast
|
|
// test composes the same value the shell draws with (see compositeOver).
|
|
inline constexpr double kLoopSpanFillAlpha = 0.20;
|
|
|
|
// The card name strip's scrim: bg/base composited at this alpha UNDER the name text, so
|
|
// text/primary stays readable when a loud capture's waveform peak (accent/primary) reaches
|
|
// into the strip. Named HERE, same reason as kLoopSpanFillAlpha above — test_theme.cpp composes
|
|
// this exact value against the strip's worst-case background (accent/primary) to pin the 4.5:1
|
|
// body floor Font::Micro answers to.
|
|
inline constexpr double kCardNameScrimAlpha = 0.75;
|
|
|
|
// Relative luminance per WCAG 2.1 (sRGB linearization + 0.2126/0.7152/0.0722 weighting). Alpha
|
|
// is ignored — a translucent overlay's effective color is the caller's to compose first
|
|
// (compositeOver).
|
|
double relativeLuminance(const KitColor& c);
|
|
|
|
// WCAG contrast ratio between two colors, in [1, 21]. Symmetric.
|
|
double contrastRatio(const KitColor& a, const KitColor& b);
|
|
|
|
// Contrast floor a text class must clear: 4.5 for Body, 3.0 for Large. test_theme.cpp asserts
|
|
// contrastRatio(text, surface) >= textFloor(class) for every pair the kit actually draws.
|
|
double textFloor(TextClass cls);
|
|
|
|
} // namespace reasampler::ui
|