repairClaymorphismExtras function
Family repairs after labels and display texts (called from
repairTheme for ThemeFamily.claymorphism specs only): lifts light
digits / darkens light function keys off the body, raises the
highlight, re-solves the function label on its ring, recomputes every
drop shadow from the final faces and re-solves the focus ring over
them. Null when unrepairable.
Implementation
Palette? repairClaymorphismExtras(
Palette p,
ThemeSpec spec,
GuardrailLimits lim,
List<int> bgs,
) {
final bg = spec.background;
if (bg.colors.length != 9) return null;
final base = bg.colors[0] | 0xFF000000;
final baseL = _lightness(base);
final dark = spec.isDark;
final policy = lim.labelPolicy;
final shade = spec.keyStyle.edgeShadePermille;
var out = p;
KeyColors? moved(KeyColors k, double dl) {
final f = argbToOklch(k.face);
final face = oklchToArgb(_clamp(f.l + dl, 0, 1), f.c, f.h);
return repairKeyLabel(k.copyWith(face: face), policy, [face]);
}
// 1. Keys off the body (light themes; night ranges make it unreachable).
if (dark) {
for (final (_, g) in _groups) {
if (_lightness(_keyOf(out, g).face) - baseL <
clayNightMinKeyAboveBodyMilli / 1000) {
return null;
}
}
} else {
final c = bg.colors;
bool digitOk(int f) =>
deltaEOKArgb(f, c[0]) >= 0.045 &&
deltaEOKArgb(f, c[3]) >= 0.045 &&
deltaEOKArgb(f, c[6]) >= 0.045 &&
deltaEOKArgb(f, c[1]) >= 0.025;
for (var i = 0; i < 4 && !digitOk(out.digit.face); i++) {
if (_lightness(out.digit.face) + 0.015 >
clayDigitMaxLMilli / 1000 + 1e-9) {
break;
}
final k = moved(out.digit, 0.015);
if (k == null) return null;
out = out.copyWith(digit: k);
}
if (!digitOk(out.digit.face)) return null;
bool fnOk(int f) =>
deltaEOKArgb(f, base) >= 0.045 &&
baseL - _lightness(f) >= 0.03 &&
minCvdDeltaE(f, base) >= clayFunctionMinCvdDeltaE;
for (var i = 0; i < 6 && !fnOk(out.function.face); i++) {
final k = moved(out.function, -0.015);
if (k == null) return null;
out = out.copyWith(function: k);
}
if (!fnOk(out.function.face)) return null;
}
// 2. The highlight.
bool lifted(int face, int highlight) =>
_lightness(clayHighlightComposite(face, highlight)) - _lightness(face) >=
clayMinHighlightLift;
bool allLifted(Palette x) =>
lifted(x.digit.face, x.highlight) &&
lifted(x.operator.face, x.highlight) &&
lifted(x.equals.face, x.highlight) &&
lifted(x.function.face, x.highlight) &&
lifted(x.surface, x.highlight);
while (!allLifted(out)) {
final a = (out.highlight >>> 24) & 0xFF;
if (a >= clayHighlightAlpha.hi) break;
final next = a + 8 > clayHighlightAlpha.hi ? clayHighlightAlpha.hi : a + 8;
out = out.copyWith(highlight: withAlpha(0xFFFFFFFF, next));
}
if (!lifted(out.digit.face, out.highlight)) {
final floor = _max(baseL + clayDigitAboveBodyMilli / 1000, 0.915);
for (var i = 0; i < 6 && !lifted(out.digit.face, out.highlight); i++) {
if (_lightness(out.digit.face) - 0.01 < floor - 1e-9) break;
final k = moved(out.digit, -0.01);
if (k == null) return null;
out = out.copyWith(digit: k);
}
}
if (!allLifted(out)) return null;
// 3. The function label on its ring.
final ring = clayLabelRingColor(out.function, shade, out.highlight);
if (contrastRatio(out.function.label, ring) < lim.labelRatio) {
final label = solveOnAll(out.function.label, [
out.function.face,
ring,
], lim.labelRatio);
if (label == null) return null;
out = out.copyWith(function: out.function.copyWith(label: label));
}
// 4. Drop shadows from the final faces, then the focus ring where it
// crosses them.
out = _withEdges(out, base, dark: dark);
final ringOn = [...bgs, ...clayFocusShadowSamples(out, bg.colors)];
final ringFix = solveOnAll(out.focusRing, ringOn, lim.focusRingRatio);
if (ringFix == null) return null;
return out.copyWith(focusRing: ringFix);
}