From e178e8924ec3c92ea2ef3336113f73f56367e8f5 Mon Sep 17 00:00:00 2001 From: another-simple-pixel Date: Wed, 13 May 2026 14:48:23 -0700 Subject: [PATCH] formula dev tools: add pattern + bg-lightness controls for dark/black MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LIGHT had three pattern/background controls — Pattern depth, Pattern chroma, BG Lightness — that DARK and BLACK didn't expose. Without them the founder couldn't tune the wallpaper pattern's brightness/saturation or nudge the background's lightness independently of the bubble cluster in those themes. Add the same three sliders to DARK and BLACK. generateSchemeDark / generateSchemeBlack gain three new optional params that pass through generateDarkFromSlots: - patternDepth: when set, replaces the slot's hard-coded lightnessMult (and patternIntensity multiplier) directly, matching LIGHT's semantic of "multiplier on effStep". - patternChroma: when set, overrides the pattern slot's chroma — taking precedence over BLACK's max-pin behaviour so the user can pull it back from the gamut edge. - bgLOffset: nudges only the background slot's L (other slots stay at their formula-computed L), mirroring LIGHT. null defaults preserve the existing behaviour, so nothing changes for callers that don't pass the new params. Defaults derivation in FormulaDevTools picks up patternDepth and patternChroma from the preset's stored tint slot (same shape as LIGHT, with the sign of the depth swapped because dark themes have the pattern brighter than the bg, not darker). bgLOffset stays at its existing 0f fallback. Slider ranges: BLACK pattern depth uses 0..15 because the default lightnessMult for BLACK pattern is 9.0 — the LIGHT/DARK 0..10 wouldn't give meaningful headroom. Co-Authored-By: Claude Opus 4.6 --- .../chat/simplex/common/ui/theme/Color.kt | 25 +++++++++++++++---- .../common/views/usersettings/Appearance.kt | 14 +++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt index a37c47950a..6344855019 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt @@ -309,25 +309,32 @@ fun generateSchemeDark( step: Float = 0.038f, mutedChroma: Float = 0.020f, colorChroma: Float = 0.063f, + patternDepth: Float? = null, + patternChroma: Float? = null, + bgLOffset: Float = 0f, saturationScale: Float = 1f, contrastScale: Float = 1f, patternIntensity: Float = 1f, -): FormulaResult = generateDarkFromSlots(hue, bgL, step, mutedChroma, colorChroma, DARK_SLOTS, false, saturationScale, contrastScale, patternIntensity) +): FormulaResult = generateDarkFromSlots(hue, bgL, step, mutedChroma, colorChroma, DARK_SLOTS, false, patternDepth, patternChroma, bgLOffset, saturationScale, contrastScale, patternIntensity) fun generateSchemeBlack( hue: Float, step: Float = 0.04f, colorChroma: Float = 0.0522f, + patternDepth: Float? = null, + patternChroma: Float? = null, + bgLOffset: Float = 0f, saturationScale: Float = 1f, contrastScale: Float = 1f, patternIntensity: Float = 1f, -): FormulaResult = generateDarkFromSlots(hue, 0f, step, 0f, colorChroma, BLACK_SLOTS, true, saturationScale, contrastScale, patternIntensity) +): FormulaResult = generateDarkFromSlots(hue, 0f, step, 0f, colorChroma, BLACK_SLOTS, true, patternDepth, patternChroma, bgLOffset, saturationScale, contrastScale, patternIntensity) private fun generateDarkFromSlots( hue: Float, bgL: Float, step: Float, mutedChroma: Float, colorChroma: Float, slotDefs: Map, patternPinsToP3: Boolean, + patternDepth: Float?, patternChroma: Float?, bgLOffset: Float, saturationScale: Float, contrastScale: Float, patternIntensity: Float, ): FormulaResult { val effStep = step * contrastScale @@ -345,12 +352,20 @@ private fun generateDarkFromSlots( var lMult = def.lightnessMult var baseC = baselineC[name]!! if (name == "pattern") { - lMult *= patternIntensity - baseC = bgCAnchor + (baseC - bgCAnchor) * patternIntensity + // patternDepth slider overrides slot's lightnessMult (and patternIntensity) + // directly; same semantic as LIGHT's patternDepth — multiplier on effStep. + lMult = patternDepth ?: (lMult * patternIntensity) + baseC = patternChroma ?: (bgCAnchor + (baseC - bgCAnchor) * patternIntensity) } - val L = bgL + lMult * effStep + // bgLOffset nudges only the background slot, leaving bubbles/pattern at their + // formula-computed L (mirrors LIGHT's bgLOffset behaviour). + val L = if (name == "background") (bgL + bgLOffset).coerceIn(0f, 1f) + else bgL + lMult * effStep val C = when { name == "background" -> bgCAnchor + // patternChroma slider takes precedence over BLACK's max-pin behavior so + // the user can dial it back from the gamut edge. + name == "pattern" && patternChroma != null -> patternChroma name == "pattern" && patternPinsToP3 -> maxChroma(L, hue) else -> bgCAnchor + (baseC - bgCAnchor) * saturationScale }.let { min(it, maxChroma(L, hue)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index 808d39ce86..79ae765ffd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -784,6 +784,8 @@ object AppearanceScope { "hue" to bg.H.let { if (isBlack) sm.H else it }, "bgL" to bg.L, "step" to step, "mutedChroma" to mutedC, "colorChroma" to sm.C, + "patternDepth" to if (step > 0f) (tint.L - bg.L) / step else 0f, + "patternChroma" to tint.C, ) } } @@ -816,10 +818,16 @@ object AppearanceScope { ) DefaultTheme.BLACK -> generateSchemeBlack( hue.floatValue, step.floatValue, colorChroma.floatValue, + patternDepth = patternDepth.floatValue, + patternChroma = patternChromaVal.floatValue, + bgLOffset = bgLOffset.floatValue, ) else -> generateSchemeDark( hue.floatValue, bgL.floatValue, step.floatValue, mutedChroma.floatValue, colorChroma.floatValue, + patternDepth = patternDepth.floatValue, + patternChroma = patternChromaVal.floatValue, + bgLOffset = bgLOffset.floatValue, ) } @@ -859,15 +867,21 @@ object AppearanceScope { } DefaultTheme.BLACK -> { FormulaSlider("Hue", hue, 0f..360f) + FormulaSlider("BG Lightness", bgLOffset, -0.05f..0.05f) FormulaSlider("Contrast", step, 0.01f..0.10f) FormulaSlider("Accent chroma", colorChroma, 0f..0.20f) + FormulaSlider("Pattern depth", patternDepth, 0f..15f) + FormulaSlider("Pattern chroma", patternChromaVal, 0f..0.15f) } else -> { FormulaSlider("Hue", hue, 0f..360f) FormulaSlider("Lightness", bgL, 0.05f..0.30f) + FormulaSlider("BG Lightness", bgLOffset, -0.05f..0.05f) FormulaSlider("Contrast", step, 0.01f..0.10f) FormulaSlider("Accent chroma", colorChroma, 0f..0.20f) FormulaSlider("Secondary chroma", mutedChroma, 0f..0.05f) + FormulaSlider("Pattern depth", patternDepth, 0f..10f) + FormulaSlider("Pattern chroma", patternChromaVal, 0f..0.15f) } } SectionItemView({