| /* |
| * Copyright 2025 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package dynamiccolor |
| |
| import contrast.Contrast |
| import dynamiccolor.DynamicScheme.Platform |
| import dynamiccolor.ToneDeltaPair.DeltaConstraint |
| import dynamiccolor.ToneDeltaPair.TonePolarity |
| import hct.Hct |
| import palettes.TonalPalette |
| import kotlin.math.max |
| import kotlin.math.min |
| |
| /** [ColorSpec] implementation for the 2025 spec. */ |
| open class ColorSpec2025 : ColorSpec2021() { |
| // //////////////////////////////////////////////////////////////// |
| // Surfaces [S] // |
| // //////////////////////////////////////////////////////////////// |
| override val background: DynamicColor |
| get() { |
| // Remapped to surface for 2025 spec. |
| val color2025 = surface.copy(name = "background") |
| return super.background.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onBackground: DynamicColor |
| get() { |
| // Remapped to onSurface for 2025 spec. |
| val color2025 = |
| onSurface.copy( |
| name = "on_background", |
| tone = { scheme -> |
| if (scheme.platform == Platform.WATCH) 100.0 else onSurface.getTone(scheme) |
| }, |
| ) |
| return super.onBackground.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val surface: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "surface", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) { |
| 4.0 |
| } else { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| 99.0 |
| } else if (scheme.variant == Variant.VIBRANT) { |
| 97.0 |
| } else { |
| 98.0 |
| } |
| } |
| } else { |
| 0.0 |
| } |
| }, |
| isBackground = true, |
| ) |
| return super.surface.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val surfaceDim: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "surface_dim", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> |
| if (scheme.isDark) { |
| 4.0 |
| } else { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| 90.0 |
| } else if (scheme.variant == Variant.VIBRANT) { |
| 85.0 |
| } else { |
| 87.0 |
| } |
| } |
| }, |
| isBackground = true, |
| chromaMultiplier = { scheme -> |
| if (!scheme.isDark) { |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 2.5 |
| Variant.TONAL_SPOT -> 1.7 |
| Variant.EXPRESSIVE -> if (Hct.isYellow(scheme.neutralPalette.hue)) 2.7 else 1.75 |
| Variant.VIBRANT -> 1.36 |
| else -> 1.0 |
| } |
| } else { |
| 1.0 |
| } |
| }, |
| ) |
| return super.surfaceDim.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val surfaceBright: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "surface_bright", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> |
| if (scheme.isDark) { |
| 18.0 |
| } else { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| 99.0 |
| } else if (scheme.variant == Variant.VIBRANT) { |
| 97.0 |
| } else { |
| 98.0 |
| } |
| } |
| }, |
| isBackground = true, |
| chromaMultiplier = { scheme -> |
| if (scheme.isDark) { |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 2.5 |
| Variant.TONAL_SPOT -> 1.7 |
| Variant.EXPRESSIVE -> if (Hct.isYellow(scheme.neutralPalette.hue)) 2.7 else 1.75 |
| Variant.VIBRANT -> 1.36 |
| else -> 1.0 |
| } |
| } else { |
| 1.0 |
| } |
| }, |
| ) |
| return super.surfaceBright.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val surfaceContainerLowest: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "surface_container_lowest", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> if (scheme.isDark) 0.0 else 100.0 }, |
| isBackground = true, |
| ) |
| return super.surfaceContainerLowest.extendSpecVersion( |
| ColorSpec.SpecVersion.SPEC_2025, |
| color2025, |
| ) |
| } |
| |
| override val surfaceContainerLow: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "surface_container_low", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) { |
| 6.0 |
| } else { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| 98.0 |
| } else if (scheme.variant == Variant.VIBRANT) { |
| 95.0 |
| } else { |
| 96.0 |
| } |
| } |
| } else { |
| 15.0 |
| } |
| }, |
| isBackground = true, |
| chromaMultiplier = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 1.3 |
| Variant.TONAL_SPOT -> 1.25 |
| Variant.EXPRESSIVE -> if (Hct.isYellow(scheme.neutralPalette.hue)) 1.3 else 1.15 |
| Variant.VIBRANT -> 1.08 |
| else -> 1.0 |
| } |
| } else { |
| 1.0 |
| } |
| }, |
| ) |
| return super.surfaceContainerLow.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val surfaceContainer: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "surface_container", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) { |
| 9.0 |
| } else { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| 96.0 |
| } else if (scheme.variant == Variant.VIBRANT) { |
| 92.0 |
| } else { |
| 94.0 |
| } |
| } |
| } else { |
| 20.0 |
| } |
| }, |
| isBackground = true, |
| chromaMultiplier = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 1.6 |
| Variant.TONAL_SPOT -> 1.4 |
| Variant.EXPRESSIVE -> if (Hct.isYellow(scheme.neutralPalette.hue)) 1.6 else 1.3 |
| Variant.VIBRANT -> 1.15 |
| else -> 1.0 |
| } |
| } else { |
| 1.0 |
| } |
| }, |
| ) |
| return super.surfaceContainer.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val surfaceContainerHigh: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "surface_container_high", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) { |
| 12.0 |
| } else { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| 94.0 |
| } else if (scheme.variant == Variant.VIBRANT) { |
| 90.0 |
| } else { |
| 92.0 |
| } |
| } |
| } else { |
| 25.0 |
| } |
| }, |
| isBackground = true, |
| chromaMultiplier = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 1.9 |
| Variant.TONAL_SPOT -> 1.5 |
| Variant.EXPRESSIVE -> if (Hct.isYellow(scheme.neutralPalette.hue)) 1.95 else 1.45 |
| Variant.VIBRANT -> 1.22 |
| else -> 1.0 |
| } |
| } else { |
| 1.0 |
| } |
| }, |
| ) |
| return super.surfaceContainerHigh.extendSpecVersion( |
| ColorSpec.SpecVersion.SPEC_2025, |
| color2025, |
| ) |
| } |
| |
| override val surfaceContainerHighest: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "surface_container_highest", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> |
| if (scheme.isDark) { |
| 15.0 |
| } else { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| 92.0 |
| } else if (scheme.variant == Variant.VIBRANT) { |
| 88.0 |
| } else { |
| 90.0 |
| } |
| } |
| }, |
| isBackground = true, |
| chromaMultiplier = { scheme -> |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 2.2 |
| Variant.TONAL_SPOT -> 1.7 |
| Variant.EXPRESSIVE -> if (Hct.isYellow(scheme.neutralPalette.hue)) 2.3 else 1.6 |
| Variant.VIBRANT -> 1.29 |
| else -> 1.0 |
| } |
| }, |
| ) |
| return super.surfaceContainerHighest.extendSpecVersion( |
| ColorSpec.SpecVersion.SPEC_2025, |
| color2025, |
| ) |
| } |
| |
| override val onSurface: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_surface", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> |
| if (scheme.variant == Variant.VIBRANT) { |
| tMaxC(scheme.neutralPalette, 0.0, 100.0, 1.1) |
| } else { |
| DynamicColor.getInitialToneFromBackground { scheme: DynamicScheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| surfaceContainerHigh |
| } |
| } |
| .invoke(scheme) |
| } |
| }, |
| chromaMultiplier = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 2.2 |
| Variant.TONAL_SPOT -> 1.7 |
| Variant.EXPRESSIVE -> { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| (if (scheme.isDark) 3.0 else 2.3) |
| } else { |
| 1.6 |
| } |
| } |
| else -> 1.0 |
| } |
| } else { |
| 1.0 |
| } |
| }, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| surfaceContainerHigh |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.isDark && scheme.platform == Platform.PHONE) { |
| getContrastCurve(11.0) |
| } else { |
| getContrastCurve(9.0) |
| } |
| }, |
| ) |
| return super.onSurface.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val surfaceVariant: DynamicColor |
| get() { |
| // Remapped to surfaceContainerHighest for 2025 spec. |
| val color2025 = surfaceContainerHighest.copy(name = "surface_variant") |
| return super.surfaceVariant.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onSurfaceVariant: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_surface_variant", |
| palette = { scheme -> scheme.neutralPalette }, |
| chromaMultiplier = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 2.2 |
| Variant.TONAL_SPOT -> 1.7 |
| Variant.EXPRESSIVE -> { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| (if (scheme.isDark) 3.0 else 2.3) |
| } else { |
| 1.6 |
| } |
| } |
| else -> 1.0 |
| } |
| } else { |
| 1.0 |
| } |
| }, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| surfaceContainerHigh |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| (if (scheme.isDark) getContrastCurve(6.0) else getContrastCurve(4.5)) |
| } else { |
| getContrastCurve(7.0) |
| } |
| }, |
| ) |
| return super.onSurfaceVariant.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val inverseSurface: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "inverse_surface", |
| palette = { scheme -> scheme.neutralPalette }, |
| tone = { scheme -> if (scheme.isDark) 98.0 else 4.0 }, |
| isBackground = true, |
| ) |
| return super.inverseSurface.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val inverseOnSurface: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "inverse_on_surface", |
| palette = { scheme -> scheme.neutralPalette }, |
| background = { inverseSurface }, |
| contrastCurve = { getContrastCurve(7.0) }, |
| ) |
| return super.inverseOnSurface.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val outline: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "outline", |
| palette = { scheme -> scheme.neutralPalette }, |
| chromaMultiplier = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 2.2 |
| Variant.TONAL_SPOT -> 1.7 |
| Variant.EXPRESSIVE -> { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| (if (scheme.isDark) 3.0 else 2.3) |
| } else { |
| 1.6 |
| } |
| } |
| else -> 1.0 |
| } |
| } else { |
| 1.0 |
| } |
| }, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| surfaceContainerHigh |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(3.0) else getContrastCurve(4.5) |
| }, |
| ) |
| return super.outline.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val outlineVariant: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "outline_variant", |
| palette = { scheme -> scheme.neutralPalette }, |
| chromaMultiplier = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 2.2 |
| Variant.TONAL_SPOT -> 1.7 |
| Variant.EXPRESSIVE -> { |
| if (Hct.isYellow(scheme.neutralPalette.hue)) { |
| (if (scheme.isDark) 3.0 else 2.3) |
| } else { |
| 1.6 |
| } |
| } |
| else -> 1.0 |
| } |
| } else { |
| 1.0 |
| } |
| }, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| surfaceContainerHigh |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(1.5) else getContrastCurve(3.0) |
| }, |
| ) |
| return super.outlineVariant.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val surfaceTint: DynamicColor |
| get() { |
| // Remapped to primary for 2025 spec. |
| val color2025 = primary.copy(name = "surface_tint") |
| return super.surfaceTint.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| // //////////////////////////////////////////////////////////////// |
| // Primaries [P] // |
| // //////////////////////////////////////////////////////////////// |
| override val primary: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "primary", |
| palette = { scheme -> scheme.primaryPalette }, |
| tone = { scheme -> |
| when { |
| scheme.variant == Variant.NEUTRAL -> { |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) 80.0 else 40.0 |
| } else { |
| 90.0 |
| } |
| } |
| scheme.variant == Variant.TONAL_SPOT -> { |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) { |
| 80.0 |
| } else { |
| tMaxC(scheme.primaryPalette) |
| } |
| } else { |
| tMaxC(scheme.primaryPalette, 0.0, 90.0) |
| } |
| } |
| scheme.variant == Variant.EXPRESSIVE -> { |
| if (scheme.platform == Platform.PHONE) { |
| tMaxC( |
| scheme.primaryPalette, |
| 0.0, |
| if (Hct.isYellow(scheme.primaryPalette.hue)) { |
| 25.0 |
| } else if (Hct.isCyan(scheme.primaryPalette.hue)) { |
| 88.0 |
| } else { |
| 98.0 |
| }, |
| ) |
| } else { // WATCH |
| tMaxC(scheme.primaryPalette) |
| } |
| } |
| else -> { // VIBRANT |
| if (scheme.platform == Platform.PHONE) { |
| tMaxC( |
| scheme.primaryPalette, |
| 0.0, |
| if (Hct.isCyan(scheme.primaryPalette.hue)) 88.0 else 98.0, |
| ) |
| } else { // WATCH |
| tMaxC(scheme.primaryPalette) |
| } |
| } |
| } |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| surfaceContainerHigh |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(4.5) else getContrastCurve(7.0) |
| }, |
| toneDeltaPair = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| ToneDeltaPair( |
| roleA = primaryContainer, |
| roleB = primary, |
| delta = 5.0, |
| polarity = TonePolarity.RELATIVE_LIGHTER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.primary.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val primaryDim: DynamicColor |
| get() { |
| return DynamicColor( |
| name = "primary_dim", |
| palette = { scheme -> scheme.primaryPalette }, |
| tone = { scheme -> |
| when (scheme.variant) { |
| Variant.NEUTRAL -> 85.0 |
| Variant.TONAL_SPOT -> tMaxC(scheme.primaryPalette, 0.0, 90.0) |
| else -> tMaxC(scheme.primaryPalette) |
| } |
| }, |
| isBackground = true, |
| background = { surfaceContainerHigh }, |
| contrastCurve = { getContrastCurve(4.5) }, |
| toneDeltaPair = { |
| ToneDeltaPair( |
| roleA = primaryDim, |
| roleB = primary, |
| delta = 5.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| }, |
| ) |
| } |
| |
| override val onPrimary: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_primary", |
| palette = { scheme -> scheme.primaryPalette }, |
| background = { scheme -> if (scheme.platform == Platform.PHONE) primary else primaryDim }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(6.0) else getContrastCurve(7.0) |
| }, |
| ) |
| return super.onPrimary.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val primaryContainer: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "primary_container", |
| palette = { scheme -> scheme.primaryPalette }, |
| tone = { scheme -> |
| when { |
| scheme.platform == Platform.WATCH -> 30.0 |
| scheme.variant == Variant.NEUTRAL -> if (scheme.isDark) 30.0 else 90.0 |
| scheme.variant == Variant.TONAL_SPOT -> |
| if (scheme.isDark) { |
| tMinC(scheme.primaryPalette, 35.0, 93.0) |
| } else { |
| tMaxC(scheme.primaryPalette, 0.0, 90.0) |
| } |
| scheme.variant == Variant.EXPRESSIVE -> |
| if (scheme.isDark) { |
| tMaxC(scheme.primaryPalette, 30.0, 93.0) |
| } else { |
| tMaxC( |
| scheme.primaryPalette, |
| 78.0, |
| if (Hct.isCyan(scheme.primaryPalette.hue)) 88.0 else 90.0, |
| ) |
| } |
| else -> { // VIBRANT |
| if (scheme.isDark) { |
| tMinC(scheme.primaryPalette, 66.0, 93.0) |
| } else { |
| tMaxC( |
| scheme.primaryPalette, |
| 66.0, |
| if (Hct.isCyan(scheme.primaryPalette.hue)) 88.0 else 93.0, |
| ) |
| } |
| } |
| } |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| null |
| } |
| }, |
| toneDeltaPair = { scheme -> |
| if (scheme.platform == Platform.WATCH) { |
| ToneDeltaPair( |
| roleA = primaryContainer, |
| roleB = primaryDim, |
| delta = 10.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| } else { |
| null |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE && scheme.contrastLevel > 0) { |
| getContrastCurve(1.5) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.primaryContainer.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onPrimaryContainer: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_primary_container", |
| palette = { scheme -> scheme.primaryPalette }, |
| background = { primaryContainer }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(6.0) else getContrastCurve(7.0) |
| }, |
| ) |
| return super.onPrimaryContainer.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val inversePrimary: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "inverse_primary", |
| palette = { scheme -> scheme.primaryPalette }, |
| tone = { scheme -> tMaxC(scheme.primaryPalette) }, |
| background = { inverseSurface }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(6.0) else getContrastCurve(7.0) |
| }, |
| ) |
| return super.inversePrimary.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| // //////////////////////////////////////////////////////////////// |
| // Secondaries [Q] // |
| // //////////////////////////////////////////////////////////////// |
| override val secondary: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "secondary", |
| palette = { scheme -> scheme.secondaryPalette }, |
| tone = { scheme -> |
| when { |
| scheme.platform == Platform.WATCH -> |
| if (scheme.variant == Variant.NEUTRAL) { |
| 90.0 |
| } else { |
| tMaxC(scheme.secondaryPalette, 0.0, 90.0) |
| } |
| scheme.variant == Variant.NEUTRAL -> |
| if (scheme.isDark) { |
| tMinC(scheme.secondaryPalette, 0.0, 98.0) |
| } else { |
| tMaxC(scheme.secondaryPalette) |
| } |
| scheme.variant == Variant.VIBRANT -> |
| tMaxC(scheme.secondaryPalette, 0.0, if (scheme.isDark) 90.0 else 98.0) |
| else -> { // EXPRESSIVE and TONAL_SPOT |
| if (scheme.isDark) 80.0 else tMaxC(scheme.secondaryPalette) |
| } |
| } |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| surfaceContainerHigh |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(4.5) else getContrastCurve(7.0) |
| }, |
| toneDeltaPair = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| ToneDeltaPair( |
| roleA = secondaryContainer, |
| roleB = secondary, |
| delta = 5.0, |
| polarity = TonePolarity.RELATIVE_LIGHTER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.secondary.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val secondaryDim: DynamicColor |
| get() { |
| return DynamicColor( |
| name = "secondary_dim", |
| palette = { scheme -> scheme.secondaryPalette }, |
| tone = { scheme -> |
| if (scheme.variant == Variant.NEUTRAL) { |
| 85.0 |
| } else { |
| tMaxC(scheme.secondaryPalette, 0.0, 90.0) |
| } |
| }, |
| isBackground = true, |
| background = { surfaceContainerHigh }, |
| contrastCurve = { getContrastCurve(4.5) }, |
| toneDeltaPair = { |
| ToneDeltaPair( |
| roleA = secondaryDim, |
| roleB = secondary, |
| delta = 5.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| }, |
| ) |
| } |
| |
| override val onSecondary: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_secondary", |
| palette = { scheme -> scheme.secondaryPalette }, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) secondary else secondaryDim |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(6.0) else getContrastCurve(7.0) |
| }, |
| ) |
| return super.onSecondary.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val secondaryContainer: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "secondary_container", |
| palette = { scheme -> scheme.secondaryPalette }, |
| tone = { scheme -> |
| when { |
| scheme.platform == Platform.WATCH -> 30.0 |
| scheme.variant == Variant.VIBRANT -> |
| if (scheme.isDark) { |
| tMinC(scheme.secondaryPalette, 30.0, 40.0) |
| } else { |
| tMaxC(scheme.secondaryPalette, 84.0, 90.0) |
| } |
| scheme.variant == Variant.EXPRESSIVE -> |
| if (scheme.isDark) 15.0 else tMaxC(scheme.secondaryPalette, 90.0, 95.0) |
| else -> if (scheme.isDark) 25.0 else 90.0 |
| } |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| null |
| } |
| }, |
| toneDeltaPair = { scheme -> |
| if (scheme.platform == Platform.WATCH) { |
| ToneDeltaPair( |
| roleA = secondaryContainer, |
| roleB = secondaryDim!!, |
| delta = 10.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| } else { |
| null |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE && scheme.contrastLevel > 0) { |
| getContrastCurve(1.5) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.secondaryContainer.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onSecondaryContainer: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_secondary_container", |
| palette = { scheme -> scheme.secondaryPalette }, |
| background = { secondaryContainer }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(6.0) else getContrastCurve(7.0) |
| }, |
| ) |
| return super.onSecondaryContainer.extendSpecVersion( |
| ColorSpec.SpecVersion.SPEC_2025, |
| color2025, |
| ) |
| } |
| |
| // //////////////////////////////////////////////////////////////// |
| // Tertiaries [T] // |
| // //////////////////////////////////////////////////////////////// |
| override val tertiary: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "tertiary", |
| palette = { scheme -> scheme.tertiaryPalette }, |
| tone = { scheme -> |
| when { |
| scheme.platform == Platform.WATCH -> |
| if (scheme.variant == Variant.TONAL_SPOT) { |
| tMaxC(scheme.tertiaryPalette, 0.0, 90.0) |
| } else { |
| tMaxC(scheme.tertiaryPalette) |
| } |
| scheme.variant == Variant.EXPRESSIVE || scheme.variant == Variant.VIBRANT -> |
| tMaxC( |
| scheme.tertiaryPalette, |
| lowerBound = 0.0, |
| upperBound = |
| if (Hct.isCyan(scheme.tertiaryPalette.hue)) { |
| 88.0 |
| } else if (scheme.isDark) { |
| 98.0 |
| } else { |
| 100.0 |
| }, |
| ) |
| else -> { // NEUTRAL and TONAL_SPOT |
| if (scheme.isDark) { |
| tMaxC(scheme.tertiaryPalette, 0.0, 98.0) |
| } else { |
| tMaxC(scheme.tertiaryPalette) |
| } |
| } |
| } |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| surfaceContainerHigh |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(4.5) else getContrastCurve(7.0) |
| }, |
| toneDeltaPair = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| ToneDeltaPair( |
| roleA = tertiaryContainer, |
| roleB = tertiary, |
| delta = 5.0, |
| polarity = TonePolarity.RELATIVE_LIGHTER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.tertiary.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val tertiaryDim: DynamicColor |
| get() { |
| return DynamicColor( |
| name = "tertiary_dim", |
| palette = { scheme -> scheme.tertiaryPalette }, |
| tone = { scheme -> |
| if (scheme.variant == Variant.TONAL_SPOT) { |
| tMaxC(scheme.tertiaryPalette, 0.0, 90.0) |
| } else { |
| tMaxC(scheme.tertiaryPalette) |
| } |
| }, |
| isBackground = true, |
| background = { surfaceContainerHigh }, |
| contrastCurve = { getContrastCurve(4.5) }, |
| toneDeltaPair = { |
| ToneDeltaPair( |
| roleA = tertiaryDim, |
| roleB = tertiary, |
| delta = 5.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| }, |
| ) |
| } |
| |
| override val onTertiary: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_tertiary", |
| palette = { scheme -> scheme.tertiaryPalette }, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) tertiary else tertiaryDim |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(6.0) else getContrastCurve(7.0) |
| }, |
| ) |
| return super.onTertiary.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val tertiaryContainer: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "tertiary_container", |
| palette = { scheme -> scheme.tertiaryPalette }, |
| tone = { scheme -> |
| when { |
| scheme.platform == Platform.WATCH -> |
| if (scheme.variant == Variant.TONAL_SPOT) { |
| tMaxC(scheme.tertiaryPalette, 0.0, 90.0) |
| } else { |
| tMaxC(scheme.tertiaryPalette) |
| } |
| scheme.variant == Variant.NEUTRAL -> |
| if (scheme.isDark) { |
| tMaxC(scheme.tertiaryPalette, 0.0, 93.0) |
| } else { |
| tMaxC(scheme.tertiaryPalette, 0.0, 96.0) |
| } |
| scheme.variant == Variant.TONAL_SPOT -> |
| tMaxC(scheme.tertiaryPalette, 0.0, if (scheme.isDark) 93.0 else 100.0) |
| scheme.variant == Variant.EXPRESSIVE -> |
| tMaxC( |
| scheme.tertiaryPalette, |
| lowerBound = 75.0, |
| upperBound = |
| if (Hct.isCyan(scheme.tertiaryPalette.hue)) { |
| 88.0 |
| } else if (scheme.isDark) { |
| 93.0 |
| } else { |
| 100.0 |
| }, |
| ) |
| else -> { // VIBRANT |
| if (scheme.isDark) { |
| tMaxC(scheme.tertiaryPalette, 0.0, 93.0) |
| } else { |
| tMaxC(scheme.tertiaryPalette, 72.0, 100.0) |
| } |
| } |
| } |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| null |
| } |
| }, |
| toneDeltaPair = { scheme -> |
| if (scheme.platform == Platform.WATCH) { |
| ToneDeltaPair( |
| roleA = tertiaryContainer, |
| roleB = tertiaryDim!!, |
| delta = 10.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| } else { |
| null |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE && scheme.contrastLevel > 0) { |
| getContrastCurve(1.5) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.tertiaryContainer.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onTertiaryContainer: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_tertiary_container", |
| palette = { scheme -> scheme.tertiaryPalette }, |
| background = { tertiaryContainer }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(6.0) else getContrastCurve(7.0) |
| }, |
| ) |
| return super.onTertiaryContainer.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| // //////////////////////////////////////////////////////////////// |
| // Errors [E] // |
| // //////////////////////////////////////////////////////////////// |
| override val error: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "error", |
| palette = { scheme -> scheme.errorPalette }, |
| tone = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) { |
| tMinC(scheme.errorPalette, 0.0, 98.0) |
| } else { |
| tMaxC(scheme.errorPalette) |
| } |
| } else { |
| tMinC(scheme.errorPalette) |
| } |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| surfaceContainerHigh |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(4.5) else getContrastCurve(7.0) |
| }, |
| toneDeltaPair = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| ToneDeltaPair( |
| roleA = errorContainer, |
| roleB = error, |
| delta = 5.0, |
| polarity = TonePolarity.RELATIVE_LIGHTER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.error.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val errorDim: DynamicColor |
| get() { |
| return DynamicColor( |
| name = "error_dim", |
| palette = { scheme -> scheme.errorPalette }, |
| tone = { scheme -> tMinC(scheme.errorPalette) }, |
| isBackground = true, |
| background = { surfaceContainerHigh }, |
| contrastCurve = { getContrastCurve(4.5) }, |
| toneDeltaPair = { |
| ToneDeltaPair( |
| roleA = errorDim, |
| roleB = error, |
| delta = 5.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| }, |
| ) |
| } |
| |
| override val onError: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_error", |
| palette = { scheme -> scheme.errorPalette }, |
| background = { scheme -> if (scheme.platform == Platform.PHONE) error else errorDim }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(6.0) else getContrastCurve(7.0) |
| }, |
| ) |
| return super.onError.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val errorContainer: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "error_container", |
| palette = { scheme -> scheme.errorPalette }, |
| tone = { scheme -> |
| if (scheme.platform == Platform.WATCH) { |
| 30.0 |
| } else { |
| if (scheme.isDark) { |
| tMinC(scheme.errorPalette, 30.0, 93.0) |
| } else { |
| tMaxC(scheme.errorPalette, 0.0, 90.0) |
| } |
| } |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| null |
| } |
| }, |
| toneDeltaPair = { scheme -> |
| if (scheme.platform == Platform.WATCH) { |
| ToneDeltaPair( |
| roleA = errorContainer, |
| roleB = errorDim!!, |
| delta = 10.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.FARTHER, |
| ) |
| } else { |
| null |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE && scheme.contrastLevel > 0) { |
| getContrastCurve(1.5) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.errorContainer.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onErrorContainer: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_error_container", |
| palette = { scheme -> scheme.errorPalette }, |
| background = { errorContainer }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE) getContrastCurve(4.5) else getContrastCurve(7.0) |
| }, |
| ) |
| return super.onErrorContainer.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| // //////////////////////////////////////////////////////////////// |
| // Primary Fixed Colors [PF] // |
| // //////////////////////////////////////////////////////////////// |
| override val primaryFixed: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "primary_fixed", |
| palette = { scheme -> scheme.primaryPalette }, |
| tone = { scheme -> |
| val tempS = DynamicScheme.from(scheme, isDark = false, contrastLevel = 0.0) |
| primaryContainer.getTone(tempS) |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| null |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE && scheme.contrastLevel > 0) { |
| getContrastCurve(1.5) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.primaryFixed.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val primaryFixedDim: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "primary_fixed_dim", |
| palette = { scheme -> scheme.primaryPalette }, |
| tone = { scheme -> primaryFixed.getTone(scheme) }, |
| isBackground = true, |
| toneDeltaPair = { |
| ToneDeltaPair( |
| roleA = primaryFixedDim, |
| roleB = primaryFixed, |
| delta = 5.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.EXACT, |
| ) |
| }, |
| ) |
| return super.primaryFixedDim.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onPrimaryFixed: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_primary_fixed", |
| palette = { scheme -> scheme.primaryPalette }, |
| background = { primaryFixedDim }, |
| contrastCurve = { getContrastCurve(7.0) }, |
| ) |
| return super.onPrimaryFixed.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onPrimaryFixedVariant: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_primary_fixed_variant", |
| palette = { scheme -> scheme.primaryPalette }, |
| background = { primaryFixedDim }, |
| contrastCurve = { getContrastCurve(4.5) }, |
| ) |
| return super.onPrimaryFixedVariant.extendSpecVersion( |
| ColorSpec.SpecVersion.SPEC_2025, |
| color2025, |
| ) |
| } |
| |
| // //////////////////////////////////////////////////////////////// |
| // Secondary Fixed Colors [QF] // |
| // //////////////////////////////////////////////////////////////// |
| override val secondaryFixed: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "secondary_fixed", |
| palette = { scheme -> scheme.secondaryPalette }, |
| tone = { scheme -> |
| val tempS = DynamicScheme.from(scheme, isDark = false, contrastLevel = 0.0) |
| secondaryContainer.getTone(tempS) |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| null |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE && scheme.contrastLevel > 0) { |
| getContrastCurve(1.5) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.secondaryFixed.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val secondaryFixedDim: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "secondary_fixed_dim", |
| palette = { scheme -> scheme.secondaryPalette }, |
| tone = { scheme -> secondaryFixed.getTone(scheme) }, |
| isBackground = true, |
| toneDeltaPair = { |
| ToneDeltaPair( |
| roleA = secondaryFixedDim, |
| roleB = secondaryFixed, |
| delta = 5.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.EXACT, |
| ) |
| }, |
| ) |
| return super.secondaryFixedDim.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onSecondaryFixed: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_secondary_fixed", |
| palette = { scheme -> scheme.secondaryPalette }, |
| background = { secondaryFixedDim }, |
| contrastCurve = { getContrastCurve(7.0) }, |
| ) |
| return super.onSecondaryFixed.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onSecondaryFixedVariant: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_secondary_fixed_variant", |
| palette = { scheme -> scheme.secondaryPalette }, |
| background = { secondaryFixedDim }, |
| contrastCurve = { getContrastCurve(4.5) }, |
| ) |
| return super.onSecondaryFixedVariant.extendSpecVersion( |
| ColorSpec.SpecVersion.SPEC_2025, |
| color2025, |
| ) |
| } |
| |
| // //////////////////////////////////////////////////////////////// |
| // Tertiary Fixed Colors [TF] // |
| // //////////////////////////////////////////////////////////////// |
| override val tertiaryFixed: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "tertiary_fixed", |
| palette = { scheme -> scheme.tertiaryPalette }, |
| tone = { scheme -> |
| val tempS = DynamicScheme.from(scheme, isDark = false, contrastLevel = 0.0) |
| tertiaryContainer.getTone(tempS) |
| }, |
| isBackground = true, |
| background = { scheme -> |
| if (scheme.platform == Platform.PHONE) { |
| if (scheme.isDark) surfaceBright else surfaceDim |
| } else { |
| null |
| } |
| }, |
| contrastCurve = { scheme -> |
| if (scheme.platform == Platform.PHONE && scheme.contrastLevel > 0) { |
| getContrastCurve(1.5) |
| } else { |
| null |
| } |
| }, |
| ) |
| return super.tertiaryFixed.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val tertiaryFixedDim: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "tertiary_fixed_dim", |
| palette = { scheme -> scheme.tertiaryPalette }, |
| tone = { scheme -> tertiaryFixed.getTone(scheme) }, |
| isBackground = true, |
| toneDeltaPair = { |
| ToneDeltaPair( |
| roleA = tertiaryFixedDim, |
| roleB = tertiaryFixed, |
| delta = 5.0, |
| polarity = TonePolarity.DARKER, |
| constraint = DeltaConstraint.EXACT, |
| ) |
| }, |
| ) |
| return super.tertiaryFixedDim.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onTertiaryFixed: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_tertiary_fixed", |
| palette = { scheme -> scheme.tertiaryPalette }, |
| background = { tertiaryFixedDim }, |
| contrastCurve = { getContrastCurve(7.0) }, |
| ) |
| return super.onTertiaryFixed.extendSpecVersion(ColorSpec.SpecVersion.SPEC_2025, color2025) |
| } |
| |
| override val onTertiaryFixedVariant: DynamicColor |
| get() { |
| val color2025 = |
| DynamicColor( |
| name = "on_tertiary_fixed_variant", |
| palette = { scheme -> scheme.tertiaryPalette }, |
| background = { tertiaryFixedDim }, |
| contrastCurve = { getContrastCurve(4.5) }, |
| ) |
| return super.onTertiaryFixedVariant.extendSpecVersion( |
| ColorSpec.SpecVersion.SPEC_2025, |
| color2025, |
| ) |
| } |
| |
| // ///////////////////////////////////////////////////////////////// |
| // Color value calculations // |
| // ///////////////////////////////////////////////////////////////// |
| override fun getHct(scheme: DynamicScheme, color: DynamicColor): Hct { |
| // This is crucial for aesthetics: we aren't simply the taking the standard color |
| // and changing its tone for contrast. Rather, we find the tone for contrast, then |
| // use the specified chroma from the palette to construct a new color. |
| // |
| // For example, this enables colors with standard tone of T90, which has limited chroma, to |
| // "recover" intended chroma as contrast increases. |
| val palette = color.palette.invoke(scheme) |
| val tone = getTone(scheme, color) |
| val hue = palette.hue |
| val chromaMultiplier = color.chromaMultiplier?.invoke(scheme) ?: 1.0 |
| val chroma = palette.chroma * chromaMultiplier |
| return Hct.from(hue, chroma, tone) |
| } |
| |
| override fun getTone(scheme: DynamicScheme, color: DynamicColor): Double { |
| val toneDeltaPair = color.toneDeltaPair?.invoke(scheme) |
| |
| // Case 0: tone delta pair. |
| if (toneDeltaPair != null) { |
| val roleA = toneDeltaPair.roleA |
| val roleB = toneDeltaPair.roleB |
| val polarity = toneDeltaPair.polarity |
| val constraint = toneDeltaPair.constraint |
| val absoluteDelta = |
| if ( |
| polarity == TonePolarity.DARKER || |
| (polarity == TonePolarity.RELATIVE_LIGHTER && scheme.isDark) || |
| (polarity == TonePolarity.RELATIVE_DARKER && !scheme.isDark) |
| ) { |
| -toneDeltaPair.delta |
| } else { |
| toneDeltaPair.delta |
| } |
| val amRoleA = color.name == roleA.name |
| val selfRole = if (amRoleA) roleA else roleB |
| val referenceRole = if (amRoleA) roleB else roleA |
| var selfTone = selfRole.tone.invoke(scheme) |
| val referenceTone = referenceRole.getTone(scheme) |
| val relativeDelta = absoluteDelta * (if (amRoleA) 1 else -1) |
| when (constraint) { |
| DeltaConstraint.EXACT -> selfTone = (referenceTone + relativeDelta).coerceIn(0.0, 100.0) |
| DeltaConstraint.NEARER -> |
| if (relativeDelta > 0) { |
| selfTone = |
| selfTone.coerceIn(referenceTone, referenceTone + relativeDelta).coerceIn(0.0, 100.0) |
| } else { |
| selfTone = |
| selfTone.coerceIn(referenceTone + relativeDelta, referenceTone).coerceIn(0.0, 100.0) |
| } |
| DeltaConstraint.FARTHER -> |
| if (relativeDelta > 0) { |
| selfTone = selfTone.coerceIn(referenceTone + relativeDelta, 100.0) |
| } else { |
| selfTone = selfTone.coerceIn(0.0, referenceTone + relativeDelta) |
| } |
| } |
| val background = color.background?.invoke(scheme) |
| val contrastCurve = color.contrastCurve?.invoke(scheme) |
| if (background != null && contrastCurve != null) { |
| val bgTone = background.getTone(scheme) |
| val selfContrast = contrastCurve.get(scheme.contrastLevel) |
| selfTone = |
| if ( |
| Contrast.ratioOfTones(bgTone, selfTone) >= selfContrast && scheme.contrastLevel >= 0 |
| ) { |
| selfTone |
| } else { |
| DynamicColor.foregroundTone(bgTone, selfContrast) |
| } |
| } |
| |
| // This can avoid the awkward tones for background colors including the access fixed colors. |
| // Accent fixed dim colors should not be adjusted. |
| if (color.isBackground && !color.name.endsWith("_fixed_dim")) { |
| selfTone = |
| if (selfTone >= 57) { |
| selfTone.coerceIn(65.0, 100.0) |
| } else { |
| selfTone.coerceIn(0.0, 49.0) |
| } |
| } |
| return selfTone |
| } else { |
| // Case 1: No tone delta pair; just solve for itself. |
| var answer = color.tone.invoke(scheme) |
| val background = color.background?.invoke(scheme) |
| val contrastCurve = color.contrastCurve?.invoke(scheme) |
| if (background == null || contrastCurve == null) { |
| return answer // No adjustment for colors with no background. |
| } |
| val bgTone = background.getTone(scheme) |
| val desiredRatio = contrastCurve.get(scheme.contrastLevel) |
| |
| // Recalculate the tone from desired contrast ratio if the current |
| // contrast ratio is not enough or desired contrast level is decreasing |
| // (<0). |
| answer = |
| if (Contrast.ratioOfTones(bgTone, answer) >= desiredRatio && scheme.contrastLevel >= 0) { |
| answer |
| } else { |
| DynamicColor.foregroundTone(bgTone, desiredRatio) |
| } |
| |
| // This can avoid the awkward tones for background colors including the access fixed colors. |
| // Accent fixed dim colors should not be adjusted. |
| if (color.isBackground && !color.name.endsWith("_fixed_dim")) { |
| answer = |
| if (answer >= 57) { |
| answer.coerceIn(65.0, 100.0) |
| } else { |
| answer.coerceIn(0.0, 49.0) |
| } |
| } |
| val secondBackground = color.secondBackground?.invoke(scheme) |
| if (secondBackground == null) { |
| return answer |
| } |
| |
| // Case 2: Adjust for dual backgrounds. |
| val bgTone1 = background.getTone(scheme) |
| val bgTone2 = secondBackground.getTone(scheme) |
| val upper = max(bgTone1, bgTone2) |
| val lower = 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. |
| val lightOption = Contrast.lighter(upper, desiredRatio) |
| |
| // The lightest dark tone that satisfies the desired ratio, |
| // or -1 if such ratio cannot be reached. |
| val darkOption = Contrast.darker(lower, desiredRatio) |
| |
| // Tones suitable for the foreground. |
| val availables = mutableListOf<Double>() |
| if (lightOption != null) { |
| availables.add(lightOption) |
| } |
| if (darkOption != null) { |
| availables.add(darkOption) |
| } |
| val prefersLight = |
| DynamicColor.tonePrefersLightForeground(bgTone1) || |
| DynamicColor.tonePrefersLightForeground(bgTone2) |
| if (prefersLight) { |
| return lightOption ?: 100.0 |
| } |
| return if (availables.size == 1) { |
| availables[0] |
| } else if (darkOption == null) { |
| 0.0 |
| } else { |
| darkOption |
| } |
| } |
| } |
| |
| // //////////////////////////////////////////////////////////////// |
| // Scheme Palettes // |
| // //////////////////////////////////////////////////////////////// |
| override fun getPrimaryPalette( |
| variant: Variant, |
| sourceColorHct: Hct, |
| isDark: Boolean, |
| platform: Platform, |
| contrastLevel: Double, |
| ): TonalPalette { |
| return when (variant) { |
| Variant.NEUTRAL -> |
| TonalPalette.fromHueAndChroma( |
| sourceColorHct.hue, |
| if (platform == Platform.PHONE) { |
| (if (Hct.isBlue(sourceColorHct.hue)) 12.0 else 8.0) |
| } else if (Hct.isBlue(sourceColorHct.hue)) { |
| 16.0 |
| } else { |
| 12.0 |
| }, |
| ) |
| Variant.TONAL_SPOT -> |
| TonalPalette.fromHueAndChroma( |
| sourceColorHct.hue, |
| if (platform == Platform.PHONE && isDark) 26.0 else 32.0, |
| ) |
| Variant.EXPRESSIVE -> |
| TonalPalette.fromHueAndChroma( |
| sourceColorHct.hue, |
| if (platform == Platform.PHONE) { |
| if (isDark) 36.0 else 48.0 |
| } else { |
| 40.0 |
| }, |
| ) |
| Variant.VIBRANT -> |
| TonalPalette.fromHueAndChroma( |
| sourceColorHct.hue, |
| if (platform == Platform.PHONE) 74.0 else 56.0, |
| ) |
| else -> super.getPrimaryPalette(variant, sourceColorHct, isDark, platform, contrastLevel) |
| } |
| } |
| |
| override fun getSecondaryPalette( |
| variant: Variant, |
| sourceColorHct: Hct, |
| isDark: Boolean, |
| platform: Platform, |
| contrastLevel: Double, |
| ): TonalPalette { |
| return when (variant) { |
| Variant.NEUTRAL -> |
| TonalPalette.fromHueAndChroma( |
| sourceColorHct.hue, |
| if (platform == Platform.PHONE) { |
| (if (Hct.isBlue(sourceColorHct.hue)) 6.0 else 4.0) |
| } else if (Hct.isBlue(sourceColorHct.hue)) { |
| 10.0 |
| } else { |
| 6.0 |
| }, |
| ) |
| Variant.TONAL_SPOT -> TonalPalette.fromHueAndChroma(sourceColorHct.hue, 16.0) |
| Variant.EXPRESSIVE -> |
| TonalPalette.fromHueAndChroma( |
| DynamicScheme.getRotatedHue( |
| sourceColorHct, |
| doubleArrayOf(0.0, 105.0, 140.0, 204.0, 253.0, 278.0, 300.0, 333.0, 360.0), |
| doubleArrayOf(-160.0, 155.0, -100.0, 96.0, -96.0, -156.0, -165.0, -160.0), |
| ), |
| if (platform == Platform.PHONE) { |
| if (isDark) 16.0 else 24.0 |
| } else { |
| 24.0 |
| }, |
| ) |
| Variant.VIBRANT -> |
| TonalPalette.fromHueAndChroma( |
| DynamicScheme.getRotatedHue( |
| sourceColorHct, |
| doubleArrayOf(0.0, 38.0, 105.0, 140.0, 333.0, 360.0), |
| doubleArrayOf(-14.0, 10.0, -14.0, 10.0, -14.0), |
| ), |
| if (platform == Platform.PHONE) 56.0 else 36.0, |
| ) |
| else -> super.getSecondaryPalette(variant, sourceColorHct, isDark, platform, contrastLevel) |
| } |
| } |
| |
| override fun getTertiaryPalette( |
| variant: Variant, |
| sourceColorHct: Hct, |
| isDark: Boolean, |
| platform: Platform, |
| contrastLevel: Double, |
| ): TonalPalette { |
| return when (variant) { |
| Variant.NEUTRAL -> |
| TonalPalette.fromHueAndChroma( |
| DynamicScheme.getRotatedHue( |
| sourceColorHct, |
| doubleArrayOf(0.0, 38.0, 105.0, 161.0, 204.0, 278.0, 333.0, 360.0), |
| doubleArrayOf(-32.0, 26.0, 10.0, -39.0, 24.0, -15.0, -32.0), |
| ), |
| if (platform == Platform.PHONE) 20.0 else 36.0, |
| ) |
| Variant.TONAL_SPOT -> |
| TonalPalette.fromHueAndChroma( |
| DynamicScheme.getRotatedHue( |
| sourceColorHct, |
| doubleArrayOf(0.0, 20.0, 71.0, 161.0, 333.0, 360.0), |
| doubleArrayOf(-40.0, 48.0, -32.0, 40.0, -32.0), |
| ), |
| if (platform == Platform.PHONE) 28.0 else 32.0, |
| ) |
| Variant.EXPRESSIVE -> |
| TonalPalette.fromHueAndChroma( |
| DynamicScheme.getRotatedHue( |
| sourceColorHct, |
| doubleArrayOf(0.0, 105.0, 140.0, 204.0, 253.0, 278.0, 300.0, 333.0, 360.0), |
| doubleArrayOf(-165.0, 160.0, -105.0, 101.0, -101.0, -160.0, -170.0, -165.0), |
| ), |
| 48.0, |
| ) |
| Variant.VIBRANT -> |
| TonalPalette.fromHueAndChroma( |
| DynamicScheme.getRotatedHue( |
| sourceColorHct, |
| doubleArrayOf(0.0, 38.0, 71.0, 105.0, 140.0, 161.0, 253.0, 333.0, 360.0), |
| doubleArrayOf(-72.0, 35.0, 24.0, -24.0, 62.0, 50.0, 62.0, -72.0), |
| ), |
| 56.0, |
| ) |
| else -> super.getTertiaryPalette(variant, sourceColorHct, isDark, platform, contrastLevel) |
| } |
| } |
| |
| override fun getNeutralPalette( |
| variant: Variant, |
| sourceColorHct: Hct, |
| isDark: Boolean, |
| platform: Platform, |
| contrastLevel: Double, |
| ): TonalPalette { |
| return when (variant) { |
| Variant.NEUTRAL -> |
| TonalPalette.fromHueAndChroma( |
| sourceColorHct.hue, |
| if (platform == Platform.PHONE) 1.4 else 6.0, |
| ) |
| Variant.TONAL_SPOT -> |
| TonalPalette.fromHueAndChroma( |
| sourceColorHct.hue, |
| if (platform == Platform.PHONE) 5.0 else 10.0, |
| ) |
| Variant.EXPRESSIVE -> |
| TonalPalette.fromHueAndChroma( |
| getExpressiveNeutralHue(sourceColorHct), |
| getExpressiveNeutralChroma(sourceColorHct, isDark, platform), |
| ) |
| Variant.VIBRANT -> |
| TonalPalette.fromHueAndChroma( |
| getVibrantNeutralHue(sourceColorHct), |
| getVibrantNeutralChroma(sourceColorHct, platform), |
| ) |
| else -> super.getNeutralPalette(variant, sourceColorHct, isDark, platform, contrastLevel) |
| } |
| } |
| |
| override fun getNeutralVariantPalette( |
| variant: Variant, |
| sourceColorHct: Hct, |
| isDark: Boolean, |
| platform: Platform, |
| contrastLevel: Double, |
| ): TonalPalette { |
| when (variant) { |
| Variant.NEUTRAL -> |
| return TonalPalette.fromHueAndChroma( |
| sourceColorHct.hue, |
| (if (platform == Platform.PHONE) 1.4 else 6.0) * 2.2, |
| ) |
| Variant.TONAL_SPOT -> |
| return TonalPalette.fromHueAndChroma( |
| sourceColorHct.hue, |
| (if (platform == Platform.PHONE) 5.0 else 10.0) * 1.7, |
| ) |
| Variant.EXPRESSIVE -> { |
| val expressiveNeutralHue = getExpressiveNeutralHue(sourceColorHct) |
| val expressiveNeutralChroma = getExpressiveNeutralChroma(sourceColorHct, isDark, platform) |
| return TonalPalette.fromHueAndChroma( |
| expressiveNeutralHue, |
| expressiveNeutralChroma * |
| if (expressiveNeutralHue >= 105 && expressiveNeutralHue < 125) 1.6 else 2.3, |
| ) |
| } |
| Variant.VIBRANT -> { |
| val vibrantNeutralHue = getVibrantNeutralHue(sourceColorHct) |
| val vibrantNeutralChroma = getVibrantNeutralChroma(sourceColorHct, platform) |
| return TonalPalette.fromHueAndChroma(vibrantNeutralHue, vibrantNeutralChroma * 1.29) |
| } |
| else -> |
| return super.getNeutralVariantPalette( |
| variant, |
| sourceColorHct, |
| isDark, |
| platform, |
| contrastLevel, |
| ) |
| } |
| } |
| |
| override fun getErrorPalette( |
| variant: Variant, |
| sourceColorHct: Hct, |
| isDark: Boolean, |
| platform: Platform, |
| contrastLevel: Double, |
| ): TonalPalette { |
| val errorHue = |
| DynamicScheme.getPiecewiseValue( |
| sourceColorHct, |
| doubleArrayOf(0.0, 3.0, 13.0, 23.0, 33.0, 43.0, 153.0, 273.0, 360.0), |
| doubleArrayOf(12.0, 22.0, 32.0, 12.0, 22.0, 32.0, 22.0, 12.0), |
| ) |
| return when (variant) { |
| Variant.NEUTRAL -> |
| TonalPalette.fromHueAndChroma(errorHue, if (platform == Platform.PHONE) 50.0 else 40.0) |
| Variant.TONAL_SPOT -> |
| TonalPalette.fromHueAndChroma(errorHue, if (platform == Platform.PHONE) 60.0 else 48.0) |
| Variant.EXPRESSIVE -> |
| TonalPalette.fromHueAndChroma(errorHue, if (platform == Platform.PHONE) 64.0 else 48.0) |
| Variant.VIBRANT -> |
| TonalPalette.fromHueAndChroma(errorHue, if (platform == Platform.PHONE) 80.0 else 60.0) |
| else -> super.getErrorPalette(variant, sourceColorHct, isDark, platform, contrastLevel) |
| } |
| } |
| |
| private fun getExpressiveNeutralHue(sourceColorHct: Hct): Double { |
| return DynamicScheme.getRotatedHue( |
| sourceColorHct, |
| doubleArrayOf(0.0, 71.0, 124.0, 253.0, 278.0, 300.0, 360.0), |
| doubleArrayOf(10.0, 0.0, 10.0, 0.0, 10.0, 0.0), |
| ) |
| } |
| |
| private fun getExpressiveNeutralChroma( |
| sourceColorHct: Hct, |
| isDark: Boolean, |
| platform: Platform, |
| ): Double { |
| val neutralHue = getExpressiveNeutralHue(sourceColorHct) |
| return if (platform == Platform.PHONE) { |
| if (isDark) { |
| if (Hct.isYellow(neutralHue)) 6.0 else 14.0 |
| } else { |
| 18.0 |
| } |
| } else { |
| 12.0 |
| } |
| } |
| |
| private fun getVibrantNeutralHue(sourceColorHct: Hct): Double { |
| return DynamicScheme.getRotatedHue( |
| sourceColorHct, |
| doubleArrayOf(0.0, 38.0, 105.0, 140.0, 333.0, 360.0), |
| doubleArrayOf(-14.0, 10.0, -14.0, 10.0, -14.0), |
| ) |
| } |
| |
| private fun getVibrantNeutralChroma(sourceColorHct: Hct, platform: Platform): Double { |
| val neutralHue = getVibrantNeutralHue(sourceColorHct) |
| return if (platform == Platform.PHONE) 28.0 else if (Hct.isBlue(neutralHue)) 28.0 else 20.0 |
| } |
| |
| private fun tMaxC( |
| palette: TonalPalette, |
| lowerBound: Double = 0.0, |
| upperBound: Double = 100.0, |
| chromaMultiplier: Double = 1.0, |
| ): Double { |
| val answer = findBestToneForChroma(palette.hue, palette.chroma * chromaMultiplier, 100.0, true) |
| return answer.coerceIn(lowerBound, upperBound) |
| } |
| |
| private fun tMinC( |
| palette: TonalPalette, |
| lowerBound: Double = 0.0, |
| upperBound: Double = 100.0, |
| ): Double { |
| val answer = findBestToneForChroma(palette.hue, palette.chroma, 0.0, false) |
| return answer.coerceIn(lowerBound, upperBound) |
| } |
| |
| private fun findBestToneForChroma( |
| hue: Double, |
| chroma: Double, |
| tone: Double, |
| byDecreasingTone: Boolean, |
| ): Double { |
| var tone = tone |
| var answer = tone |
| var bestCandidate = Hct.from(hue, chroma, answer) |
| while (bestCandidate.chroma < chroma) { |
| if (tone < 0 || tone > 100) { |
| break |
| } |
| tone += if (byDecreasingTone) -1.0 else 1.0 |
| val newCandidate = Hct.from(hue, chroma, tone) |
| if (bestCandidate.chroma < newCandidate.chroma) { |
| bestCandidate = newCandidate |
| answer = tone |
| } |
| } |
| return answer |
| } |
| |
| private fun getContrastCurve(defaultContrast: Double): ContrastCurve { |
| return when (defaultContrast) { |
| 1.5 -> ContrastCurve(1.5, 1.5, 3.0, 5.5) |
| 3.0 -> ContrastCurve(3.0, 3.0, 4.5, 7.0) |
| 4.5 -> ContrastCurve(4.5, 4.5, 7.0, 11.0) |
| 6.0 -> ContrastCurve(6.0, 6.0, 7.0, 11.0) |
| 7.0 -> ContrastCurve(7.0, 7.0, 11.0, 21.0) |
| 9.0 -> ContrastCurve(9.0, 9.0, 11.0, 21.0) |
| 11.0 -> ContrastCurve(11.0, 11.0, 21.0, 21.0) |
| 21.0 -> ContrastCurve(21.0, 21.0, 21.0, 21.0) |
| else -> ContrastCurve(defaultContrast, defaultContrast, 7.0, 21.0) |
| } |
| } |
| } |