getTone method
Implementation
@override
double getTone(DynamicScheme scheme, DynamicColor color) {
final decreasingContrast = scheme.contrastLevel < 0;
final toneDeltaPair = color.toneDeltaPair?.call(scheme);
// Case 1: dual foreground, pair of colors with delta constraint.
if (toneDeltaPair != null) {
final roleA = toneDeltaPair.roleA;
final roleB = toneDeltaPair.roleB;
final delta = toneDeltaPair.delta;
final polarity = toneDeltaPair.polarity;
final stayTogether = toneDeltaPair.stayTogether;
final aIsNearer =
(toneDeltaPair.constraint == .nearer ||
(polarity == .lighter && !scheme.isDark) ||
(polarity == .darker && !scheme.isDark));
final nearer = aIsNearer ? roleA : roleB;
final farther = aIsNearer ? roleB : roleA;
final amNearer = color.name == nearer.name;
final expansionDir = scheme.isDark ? 1 : -1;
var nTone = nearer.tone(scheme);
var fTone = farther.tone(scheme);
// 1st round: solve to min, each
if (color.background != null &&
nearer.contrastCurve != null &&
farther.contrastCurve != null) {
final bg = color.background?.call(scheme);
final nContrastCurve = nearer.contrastCurve?.call(scheme);
final fContrastCurve = farther.contrastCurve?.call(scheme);
if (bg != null && nContrastCurve != null && fContrastCurve != null) {
final nContrast = nContrastCurve.get(scheme.contrastLevel);
final fContrast = fContrastCurve.get(scheme.contrastLevel);
final bgTone = bg.getTone(scheme);
// If a color is good enough, it is not adjusted.
// Initial and adjusted tones for `nearer`
if (Contrast.ratioOfTones(bgTone, nTone) < nContrast) {
nTone = DynamicColor.foregroundTone(bgTone, nContrast);
}
// Initial and adjusted tones for `farther`
if (Contrast.ratioOfTones(bgTone, fTone) < fContrast) {
fTone = DynamicColor.foregroundTone(bgTone, fContrast);
}
if (decreasingContrast) {
// If decreasing contrast, adjust color to the "bare minimum"
// that satisfies contrast.
nTone = DynamicColor.foregroundTone(bgTone, nContrast);
fTone = DynamicColor.foregroundTone(bgTone, fContrast);
}
}
}
// If constraint is not satisfied, try another round.
if ((fTone - nTone) * expansionDir < delta) {
// 2nd round: expand farther to match delta.
fTone = MathUtils.clampDouble(0.0, 100.0, nTone + delta * expansionDir);
// If constraint is not satisfied, try another round.
if ((fTone - nTone) * expansionDir < delta) {
// 3rd round: contract nearer to match delta.
nTone = MathUtils.clampDouble(
0.0,
100.0,
fTone - delta * expansionDir,
);
}
}
// Avoids the 50-59 awkward zone.
if (50.0 <= nTone && nTone < 60.0) {
// If `nearer` is in the awkward zone, move it away, together with
// `farther`.
if (expansionDir > 0.0) {
nTone = 60.0;
fTone = math.max(fTone, nTone + delta * expansionDir);
} else {
nTone = 49.0;
fTone = math.min(fTone, nTone + delta * expansionDir);
}
} else if (50.0 <= fTone && fTone < 60.0) {
if (stayTogether) {
// Fixes both, to avoid two colors on opposite sides of the "awkward
// zone".
if (expansionDir > 0) {
nTone = 60.0;
fTone = math.max(fTone, nTone + delta * expansionDir);
} else {
nTone = 49.0;
fTone = math.min(fTone, nTone + delta * expansionDir);
}
} else {
// Not required to stay together; fixes just one.
if (expansionDir > 0) {
fTone = 60.0;
} else {
fTone = 49.0;
}
}
}
// Returns `nTone` if this color is `nearer`, otherwise `fTone`.
return amNearer ? nTone : fTone;
} else {
// Case 2: No contrast pair; just solve for itself.
var answer = color.tone(scheme);
if (color.background?.call(scheme) == null ||
color.contrastCurve?.call(scheme) == null) {
return answer; // No adjustment for colors with no background.
}
final bgTone = color.background!(scheme)!.getTone(scheme);
final desiredRatio = color.contrastCurve!(scheme)!.get(
scheme.contrastLevel,
);
if (Contrast.ratioOfTones(bgTone, answer) >= desiredRatio) {
// Don't "improve" what's good enough.
} else {
// Rough improvement.
answer = DynamicColor.foregroundTone(bgTone, desiredRatio);
}
if (decreasingContrast) {
answer = DynamicColor.foregroundTone(bgTone, desiredRatio);
}
if (color.isBackground && 50.0 <= answer && answer < 60.0) {
// Must adjust
if (Contrast.ratioOfTones(49.0, bgTone) >= desiredRatio) {
answer = 49.0;
} else {
answer = 60.0;
}
}
if (color.secondBackground?.call(scheme) == null) {
return answer;
}
// Case 3: Adjust for dual backgrounds.
final bgTone1 = color.background!(scheme)!.getTone(scheme);
final bgTone2 = color.secondBackground!(scheme)!.getTone(scheme);
final upper = math.max(bgTone1, bgTone2);
final lower = math.min(bgTone1, bgTone2);
if (Contrast.ratioOfTones(upper, answer) >= desiredRatio &&
Contrast.ratioOfTones(lower, answer) >= desiredRatio) {
return answer;
}
// The darkest light tone that satisfies the desired ratio,
// or -1 if such ratio cannot be reached.
final lightOption = Contrast.lighter(upper, desiredRatio);
// The lightest dark tone that satisfies the desired ratio,
// or -1 if such ratio cannot be reached.
final darkOption = Contrast.darker(lower, desiredRatio);
// Tones suitable for the foreground.
final availables = <double>[?lightOption, ?darkOption];
final prefersLight =
DynamicColor.tonePrefersLightForeground(bgTone1) ||
DynamicColor.tonePrefersLightForeground(bgTone2);
if (prefersLight) {
return lightOption ?? 100.0;
}
return availables.length == 1 ? availables[0] : (darkOption ?? 0.0);
}
}