Files
reasampler/src/core/ui/theme.h
T

92 lines
4.0 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, 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, anchored on the three accents (primary -> secondary -> tertiary).
#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 (pastel 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)
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,
};
// Text size classes for the WCAG floor: "Large" (>= ~18.66px, or >= ~14px 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 accent/primary (low) -> accent/secondary (mid) -> accent/tertiary (high),
// so the strip reads as an extension of the accent system rather than a separate flourish.
// t is clamped to [0, 1].
KitColor spectralColor(double t);
// --- WCAG contrast (the "punch" rule, made testable) --------------------------
// 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.
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