feat(theme): add Garnet preset with deep red accents (#1418)
This commit is contained in:
8 files changed
+203
-5
No files matched your search
@@ -25,7 +25,8 @@ app/src/main/java/com/m57/hermescontrol/theme/
|
||||
├── GruvboxScheme.kt
|
||||
├── CatppuccinScheme.kt
|
||||
├── AmoledScheme.kt # dark-only (ThemeMode.DARK_ONLY)
|
||||
└── NordScheme.kt
|
||||
├── NordScheme.kt
|
||||
└── GarnetScheme.kt
|
||||
```
|
||||
|
||||
## The template — one shape for every theme
|
||||
@@ -95,7 +96,7 @@ Enabled Material components also have a component-state contract:
|
||||
|
||||
`ThemeComponentContrastTest` checks the pinned Material role combinations in every
|
||||
resolved preset/mode. `ThemeComponentGalleryTest` reads the installed Compose
|
||||
public color defaults and verifies rendered pixels for all twelve preset/mode
|
||||
public color defaults and verifies rendered pixels for all fourteen preset/mode
|
||||
combinations, including focused/error fields. Disabled controls are deliberately
|
||||
excluded from the enabled contrast contract; Material uses opacity to mute them.
|
||||
Settings switches use Material defaults (`primary` track / `onPrimary` thumb),
|
||||
@@ -105,6 +106,14 @@ Outlines must contrast against both surface and the unchecked switch track
|
||||
(`surfaceContainerHighest`). Named palettes may need documented derived primary
|
||||
or error tones for direct text; fixed roles still preserve their upstream accents.
|
||||
|
||||
## Garnet
|
||||
|
||||
Garnet preserves Default's slate surface ladder, typography, and semantic status
|
||||
colors. Its seed is `#990000`, used as the light primary and dark primary
|
||||
container. Dark primary/text/icons use a lighter `#FFB4AB` for contrast; rose
|
||||
secondary and copper tertiary roles, their containers, fixed accents, inverse
|
||||
primary, and surface tint follow the new accent family. Both modes are shipped.
|
||||
|
||||
## Theme modes
|
||||
|
||||
A theme declares which modes it ships via the factory you build it with:
|
||||
|
||||
@@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable
|
||||
enum class ThemePreference { SYSTEM, LIGHT, DARK }
|
||||
|
||||
@Serializable
|
||||
enum class ThemePreset { DEFAULT, MONOCHROME, GRUVBOX, CATPPUCCIN, AMOLED, NORD }
|
||||
enum class ThemePreset { DEFAULT, MONOCHROME, GRUVBOX, CATPPUCCIN, AMOLED, NORD, GARNET }
|
||||
|
||||
val LocalThemePreference = compositionLocalOf { ThemePreference.SYSTEM }
|
||||
val LocalThemePreset = compositionLocalOf { ThemePreset.DEFAULT }
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.m57.hermescontrol.theme
|
||||
import com.m57.hermescontrol.theme.presets.AmoledTheme
|
||||
import com.m57.hermescontrol.theme.presets.CatppuccinTheme
|
||||
import com.m57.hermescontrol.theme.presets.DefaultTheme
|
||||
import com.m57.hermescontrol.theme.presets.GarnetTheme
|
||||
import com.m57.hermescontrol.theme.presets.GruvboxTheme
|
||||
import com.m57.hermescontrol.theme.presets.MonochromeTheme
|
||||
import com.m57.hermescontrol.theme.presets.NordTheme
|
||||
@@ -21,6 +22,7 @@ internal val ThemeRegistry =
|
||||
ThemeDefinition(ThemePreset.CATPPUCCIN, CatppuccinTheme),
|
||||
ThemeDefinition(ThemePreset.AMOLED, AmoledTheme),
|
||||
ThemeDefinition(ThemePreset.NORD, NordTheme),
|
||||
ThemeDefinition(ThemePreset.GARNET, GarnetTheme),
|
||||
)
|
||||
|
||||
internal fun ThemePreset.palette(): ThemePalette = ThemeRegistry.single { it.preset == this }.palette
|
||||
@@ -0,0 +1,185 @@
|
||||
package com.m57.hermescontrol.theme.presets
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.m57.hermescontrol.theme.HermesStatusColors
|
||||
import com.m57.hermescontrol.theme.PaletteColors
|
||||
import com.m57.hermescontrol.theme.buildTheme
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Default slate surfaces retained; accents use the Garnet red family.
|
||||
// Garnet Dark — named swatches
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
private val GarnetDarkBg0 = Color(0xFF0F1416)
|
||||
private val GarnetDarkBg1 = Color(0xFF171C1E)
|
||||
private val GarnetDarkBg2 = Color(0xFF1B2022)
|
||||
private val GarnetDarkBg3 = Color(0xFF252B2D)
|
||||
private val GarnetDarkBg4 = Color(0xFF303638)
|
||||
private val GarnetDarkFg1 = Color(0xFFDEE3E5)
|
||||
private val GarnetDarkFg3 = Color(0xFFBEC8CA)
|
||||
private val GarnetGray = Color(0xFF889294) // same neutral gray in both modes
|
||||
|
||||
// "Bright" accents — high-visibility variants against dark surfaces.
|
||||
private val GarnetBrightCopper = Color(0xFFEABD96)
|
||||
private val GarnetBrightRose = Color(0xFFE5BDB8)
|
||||
private val GarnetBrightGreen = Color(0xFF81D692)
|
||||
private val GarnetBrightYellow = Color(0xFFECC248)
|
||||
private val GarnetBrightRed = Color(0xFFFFB4AB)
|
||||
private val GarnetBrightInfo = Color(0xFF8ECEFF)
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Garnet Light — named swatches
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
private val GarnetLightBg0 = Color(0xFFF5FAFC)
|
||||
private val GarnetLightBg1 = Color(0xFFEFF4F6)
|
||||
private val GarnetLightBg2 = Color(0xFFE9EEF0)
|
||||
private val GarnetLightBg3 = Color(0xFFE3E8EA)
|
||||
private val GarnetLightBg4 = Color(0xFFDEE3E5)
|
||||
private val GarnetLightFg1 = Color(0xFF171C1E)
|
||||
private val GarnetLightFg3 = Color(0xFF3F484A)
|
||||
|
||||
// "Faded" accents — muted variants for light background contrast.
|
||||
private val GarnetAccent = Color(0xFF990000)
|
||||
private val GarnetFadedCopper = Color(0xFF805531)
|
||||
private val GarnetFadedRose = Color(0xFF775651)
|
||||
private val GarnetFadedGreen = Color(0xFF126D2B)
|
||||
private val GarnetFadedYellow = Color(0xFF725C00)
|
||||
private val GarnetFadedRed = Color(0xFFBA1A1A)
|
||||
private val GarnetFadedInfo = Color(0xFF006393)
|
||||
|
||||
// #990000 is the seed and light primary. Its contrast on the dark canvas is only
|
||||
// 2.08:1; #FFB4AB supplies readable dark-mode text/icons (10.93:1).
|
||||
private val GarnetLightRedContainer = Color(0xFFFFDAD5)
|
||||
private val GarnetDarkRoseContainer = Color(0xFF573935)
|
||||
private val GarnetDarkCopperContainer = Color(0xFF634025)
|
||||
private val GarnetLightCopperContainer = Color(0xFFFFDCC1)
|
||||
|
||||
/** Garnet — Default slate surfaces with a #990000 red accent and matching warm tones. */
|
||||
val GarnetTheme =
|
||||
buildTheme(
|
||||
dark =
|
||||
PaletteColors(
|
||||
primary = GarnetBrightRed,
|
||||
onPrimary = GarnetDarkBg0,
|
||||
primaryContainer = GarnetAccent,
|
||||
onPrimaryContainer = GarnetLightBg0,
|
||||
secondary = GarnetBrightRose,
|
||||
onSecondary = GarnetDarkBg0,
|
||||
secondaryContainer = GarnetDarkRoseContainer,
|
||||
onSecondaryContainer = GarnetBrightRose,
|
||||
tertiary = GarnetBrightCopper,
|
||||
onTertiary = GarnetDarkBg0,
|
||||
tertiaryContainer = GarnetDarkCopperContainer,
|
||||
onTertiaryContainer = GarnetBrightCopper,
|
||||
background = GarnetDarkBg0,
|
||||
onBackground = GarnetDarkFg1,
|
||||
surface = GarnetDarkBg0,
|
||||
onSurface = GarnetDarkFg1,
|
||||
surfaceVariant = GarnetDarkBg1,
|
||||
onSurfaceVariant = GarnetDarkFg3,
|
||||
surfaceDim = GarnetDarkBg0,
|
||||
surfaceBright = GarnetDarkBg4,
|
||||
primaryFixed = GarnetBrightRed,
|
||||
primaryFixedDim = GarnetBrightRed,
|
||||
onPrimaryFixed = GarnetDarkBg0,
|
||||
onPrimaryFixedVariant = GarnetDarkBg0,
|
||||
secondaryFixed = GarnetBrightRose,
|
||||
secondaryFixedDim = GarnetBrightRose,
|
||||
onSecondaryFixed = GarnetDarkBg0,
|
||||
onSecondaryFixedVariant = GarnetDarkBg0,
|
||||
tertiaryFixed = GarnetBrightCopper,
|
||||
tertiaryFixedDim = GarnetBrightCopper,
|
||||
onTertiaryFixed = GarnetDarkBg0,
|
||||
onTertiaryFixedVariant = GarnetDarkBg0,
|
||||
surfaceContainerLowest = GarnetDarkBg0,
|
||||
surfaceContainerLow = GarnetDarkBg1,
|
||||
surfaceContainer = GarnetDarkBg2,
|
||||
surfaceContainerHigh = GarnetDarkBg3,
|
||||
surfaceContainerHighest = GarnetDarkBg4,
|
||||
inverseSurface = GarnetDarkFg1,
|
||||
inverseOnSurface = GarnetDarkBg0,
|
||||
inversePrimary = GarnetAccent,
|
||||
outline = GarnetGray,
|
||||
outlineVariant = GarnetDarkBg3,
|
||||
scrim = GarnetDarkBg0,
|
||||
status =
|
||||
HermesStatusColors(
|
||||
success = GarnetBrightGreen,
|
||||
successContainer = GarnetDarkBg3,
|
||||
onSuccess = GarnetDarkBg0,
|
||||
warning = GarnetBrightYellow,
|
||||
warningContainer = GarnetDarkBg3,
|
||||
onWarning = GarnetDarkBg0,
|
||||
error = GarnetBrightRed,
|
||||
errorContainer = GarnetDarkBg3,
|
||||
onError = GarnetDarkBg0,
|
||||
onErrorContainer = GarnetBrightRed,
|
||||
info = GarnetBrightInfo,
|
||||
infoContainer = GarnetDarkBg3,
|
||||
onInfo = GarnetDarkBg0,
|
||||
),
|
||||
),
|
||||
light =
|
||||
PaletteColors(
|
||||
primary = GarnetAccent,
|
||||
onPrimary = GarnetLightBg0,
|
||||
primaryContainer = GarnetLightRedContainer,
|
||||
onPrimaryContainer = GarnetAccent,
|
||||
secondary = GarnetFadedRose,
|
||||
onSecondary = GarnetLightBg0,
|
||||
secondaryContainer = GarnetLightRedContainer,
|
||||
onSecondaryContainer = GarnetFadedRose,
|
||||
tertiary = GarnetFadedCopper,
|
||||
onTertiary = GarnetLightBg0,
|
||||
tertiaryContainer = GarnetLightCopperContainer,
|
||||
onTertiaryContainer = GarnetFadedCopper,
|
||||
background = GarnetLightBg0,
|
||||
onBackground = GarnetLightFg1,
|
||||
surface = GarnetLightBg0,
|
||||
onSurface = GarnetLightFg1,
|
||||
surfaceVariant = GarnetLightBg1,
|
||||
onSurfaceVariant = GarnetLightFg3,
|
||||
surfaceDim = GarnetLightBg4,
|
||||
surfaceBright = GarnetLightBg0,
|
||||
primaryFixed = GarnetBrightRed,
|
||||
primaryFixedDim = GarnetBrightRed,
|
||||
onPrimaryFixed = GarnetDarkBg0,
|
||||
onPrimaryFixedVariant = GarnetDarkBg0,
|
||||
secondaryFixed = GarnetBrightRose,
|
||||
secondaryFixedDim = GarnetBrightRose,
|
||||
onSecondaryFixed = GarnetDarkBg0,
|
||||
onSecondaryFixedVariant = GarnetDarkBg0,
|
||||
tertiaryFixed = GarnetBrightCopper,
|
||||
tertiaryFixedDim = GarnetBrightCopper,
|
||||
onTertiaryFixed = GarnetDarkBg0,
|
||||
onTertiaryFixedVariant = GarnetDarkBg0,
|
||||
surfaceContainerLowest = GarnetLightBg0,
|
||||
surfaceContainerLow = GarnetLightBg1,
|
||||
surfaceContainer = GarnetLightBg2,
|
||||
surfaceContainerHigh = GarnetLightBg3,
|
||||
surfaceContainerHighest = GarnetLightBg4,
|
||||
inverseSurface = GarnetLightFg1,
|
||||
inverseOnSurface = GarnetLightBg0,
|
||||
inversePrimary = GarnetBrightRed,
|
||||
outline = GarnetLightFg3,
|
||||
outlineVariant = GarnetLightBg3,
|
||||
scrim = GarnetLightBg0,
|
||||
status =
|
||||
HermesStatusColors(
|
||||
success = GarnetFadedGreen,
|
||||
successContainer = GarnetLightBg3,
|
||||
onSuccess = GarnetLightBg0,
|
||||
warning = GarnetFadedYellow,
|
||||
warningContainer = GarnetLightBg3,
|
||||
onWarning = GarnetLightBg0,
|
||||
error = GarnetFadedRed,
|
||||
errorContainer = GarnetLightBg3,
|
||||
onError = GarnetLightBg0,
|
||||
onErrorContainer = GarnetFadedRed,
|
||||
info = GarnetFadedInfo,
|
||||
infoContainer = GarnetLightBg3,
|
||||
onInfo = GarnetLightBg0,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -164,6 +164,7 @@ private fun ThemePreset.label(): String =
|
||||
ThemePreset.CATPPUCCIN -> R.string.theme_preset_catppuccin
|
||||
ThemePreset.AMOLED -> R.string.theme_preset_amoled
|
||||
ThemePreset.NORD -> R.string.theme_preset_nord
|
||||
ThemePreset.GARNET -> R.string.theme_preset_garnet
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user