mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-30 01:37:07 +00:00
ui: OKLCH colors for gradients in onboarding cards (#6859)
* ui: OKLCH colors for gradients in onboarding cards * add wide gamut to manifest
This commit is contained in:
@@ -23,17 +23,17 @@ struct OnboardingCardView: View {
|
||||
let action: () -> Void
|
||||
|
||||
static let lightStops: [Gradient.Stop] = [
|
||||
.init(color: Color(red: 0.824, green: 0.910, blue: 1.0), location: 0.0),
|
||||
.init(color: Color(red: 0.800, green: 0.914, blue: 1.0), location: 0.5),
|
||||
.init(color: Color(red: 0.875, green: 1.0, blue: 1.0), location: 0.9),
|
||||
.init(color: Color(red: 1.0, green: 0.988, blue: 0.918), location: 1.0)
|
||||
.init(color: oklch(0.9219, 0.0431, 249.4), location: 0.0),
|
||||
.init(color: oklch(0.9198, 0.0471, 240.7), location: 0.5),
|
||||
.init(color: oklch(0.9772, 0.0358, 196.6), location: 0.9),
|
||||
.init(color: oklch(0.9886, 0.0272, 99.1), location: 1.0)
|
||||
]
|
||||
|
||||
static let darkStops: [Gradient.Stop] = [
|
||||
.init(color: Color(red: 0.016, green: 0.039, blue: 0.141), location: 0.4),
|
||||
.init(color: Color(red: 0.220, green: 0.329, blue: 0.671), location: 0.72),
|
||||
.init(color: Color(red: 0.659, green: 0.929, blue: 0.953), location: 0.9),
|
||||
.init(color: Color(red: 1.0, green: 0.965, blue: 0.878), location: 1.0)
|
||||
.init(color: oklch(0.1578, 0.0609, 267.3), location: 0.4),
|
||||
.init(color: oklch(0.4729, 0.1574, 267.3), location: 0.72),
|
||||
.init(color: oklch(0.9024, 0.0760, 202.8), location: 0.9),
|
||||
.init(color: oklch(0.9744, 0.0370, 88.4), location: 1.0)
|
||||
]
|
||||
|
||||
static let gradientAngle: Double = 80.0 * .pi / 180.0
|
||||
|
||||
@@ -33,6 +33,55 @@ let HighOrLowlight = Color(139, 135, 134, a: 255)
|
||||
//let FileLight = Color(183, 190, 199, a: 255)
|
||||
//let FileDark = Color(101, 101, 106, a: 255)
|
||||
|
||||
// Create a Display P3 Color from oklch components. H in degrees
|
||||
public func oklch(_ L: Double, _ C: Double, _ H: Double, alpha: Double = 1.0) -> Color {
|
||||
let hRad = H * .pi / 180.0
|
||||
let cosH = cos(hRad)
|
||||
let sinH = sin(hRad)
|
||||
|
||||
func linearP3(C: Double) -> (Double, Double, Double) {
|
||||
let a = C * cosH
|
||||
let b = C * sinH
|
||||
// oklab → LMS (Ottosson 2021)
|
||||
let l_ = L + 0.3963377774 * a + 0.2158037573 * b
|
||||
let m_ = L - 0.1055613458 * a - 0.0638541728 * b
|
||||
let s_ = L - 0.0894841775 * a - 1.2914855480 * b
|
||||
let l = l_ * l_ * l_
|
||||
let m = m_ * m_ * m_
|
||||
let s = s_ * s_ * s_
|
||||
// LMS → linear Display P3 (direct, no sRGB clamping)
|
||||
return (
|
||||
3.1281105148 * l - 2.2570749853 * m + 0.1293047593 * s,
|
||||
-1.0911282009 * l + 2.4132668169 * m - 0.3221681599 * s,
|
||||
-0.0260136845 * l - 0.5080276339 * m + 1.5333166364 * s
|
||||
)
|
||||
}
|
||||
|
||||
func inGamut(_ r: Double, _ g: Double, _ b: Double) -> Bool {
|
||||
r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1
|
||||
}
|
||||
|
||||
// linear P3 → gamma-encoded P3 (same transfer function as sRGB)
|
||||
func gammaEncode(_ x: Double) -> Double {
|
||||
x >= 0.0031308
|
||||
? 1.055 * pow(min(x, 1.0), 1.0 / 2.4) - 0.055
|
||||
: 12.92 * max(x, 0)
|
||||
}
|
||||
|
||||
var (r, g, b) = linearP3(C: C)
|
||||
if !inGamut(r, g, b) {
|
||||
var lo = 0.0, hi = C
|
||||
while hi - lo > 1e-5 {
|
||||
let mid = (lo + hi) / 2
|
||||
let (mr, mg, mb) = linearP3(C: mid)
|
||||
if inGamut(mr, mg, mb) { lo = mid; r = mr; g = mg; b = mb }
|
||||
else { hi = mid }
|
||||
}
|
||||
}
|
||||
|
||||
return Color(.displayP3, red: gammaEncode(r), green: gammaEncode(g), blue: gammaEncode(b), opacity: alpha)
|
||||
}
|
||||
|
||||
extension Color {
|
||||
public init(_ argb: Int64) {
|
||||
let a = Double((argb & 0xFF000000) >> 24) / 255.0
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:colorMode="wideColorGamut"
|
||||
android:exported="true"
|
||||
android:label="${app_name}"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
|
||||
+55
-7
@@ -3,16 +3,64 @@ package chat.simplex.common.ui.theme
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.graphics.*
|
||||
import chat.simplex.common.views.helpers.mixWith
|
||||
import androidx.compose.ui.graphics.colorspace.ColorSpaces
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
|
||||
fun oklch(L: Float, C: Float, H: Float, alpha: Float = 1f): Color {
|
||||
val hRad = H * (Math.PI.toFloat() / 180f)
|
||||
val cosH = cos(hRad)
|
||||
val sinH = sin(hRad)
|
||||
|
||||
fun linearP3(c: Float): Triple<Float, Float, Float> {
|
||||
val a = c * cosH
|
||||
val b = c * sinH
|
||||
// oklab → LMS (Ottosson 2021)
|
||||
val l_ = L + 0.3963377774f * a + 0.2158037573f * b
|
||||
val m_ = L - 0.1055613458f * a - 0.0638541728f * b
|
||||
val s_ = L - 0.0894841775f * a - 1.2914855480f * b
|
||||
val l = l_ * l_ * l_
|
||||
val m = m_ * m_ * m_
|
||||
val s = s_ * s_ * s_
|
||||
// LMS → linear Display P3
|
||||
return Triple(
|
||||
3.1281105148f * l - 2.2570749853f * m + 0.1293047593f * s,
|
||||
-1.0911282009f * l + 2.4132668169f * m - 0.3221681599f * s,
|
||||
-0.0260136845f * l - 0.5080276339f * m + 1.5333166364f * s
|
||||
)
|
||||
}
|
||||
|
||||
fun inGamut(r: Float, g: Float, b: Float) = r in 0f..1f && g in 0f..1f && b in 0f..1f
|
||||
|
||||
// linear P3 → gamma-encoded P3 (same transfer function as sRGB)
|
||||
fun gammaEncode(x: Float): Float =
|
||||
if (x >= 0.0031308f) 1.055f * min(x, 1f).pow(1f / 2.4f) - 0.055f
|
||||
else 12.92f * max(x, 0f)
|
||||
|
||||
var (r, g, b) = linearP3(C)
|
||||
if (!inGamut(r, g, b)) {
|
||||
var lo = 0f; var hi = C
|
||||
while (hi - lo > 1e-5f) {
|
||||
val mid = (lo + hi) / 2
|
||||
val (mr, mg, mb) = linearP3(mid)
|
||||
if (inGamut(mr, mg, mb)) { lo = mid; r = mr; g = mg; b = mb }
|
||||
else hi = mid
|
||||
}
|
||||
}
|
||||
|
||||
return Color(
|
||||
red = gammaEncode(r),
|
||||
green = gammaEncode(g),
|
||||
blue = gammaEncode(b),
|
||||
alpha = alpha,
|
||||
colorSpace = ColorSpaces.DisplayP3
|
||||
)
|
||||
}
|
||||
|
||||
val Purple200 = Color(0xFFBB86FC)
|
||||
val Purple500 = Color(0xFF6200EE)
|
||||
val Purple700 = Color(0xFF3700B3)
|
||||
val Teal200 = Color(0xFF03DAC5)
|
||||
val Gray = Color(0x22222222)
|
||||
val Indigo = Color(0xFF9966FF)
|
||||
val SimplexBlue = Color(0, 136, 255, 255) // If this value changes also need to update #0088ff in string resource files
|
||||
val SimplexGreen = Color(77, 218, 103, 255)
|
||||
|
||||
+8
-8
@@ -89,17 +89,17 @@ internal fun gradientPoints(aspectRatio: Float, scale: Float): GradientEndpoints
|
||||
}
|
||||
|
||||
internal val lightStops = arrayOf(
|
||||
0.0f to Color(0xFFd2e8ff),
|
||||
0.5f to Color(0xFFcce9ff),
|
||||
0.9f to Color(0xFFdfffff),
|
||||
1.0f to Color(0xFFfffcea)
|
||||
0.0f to oklch(0.9219f, 0.0431f, 249.4f),
|
||||
0.5f to oklch(0.9198f, 0.0471f, 240.7f),
|
||||
0.9f to oklch(0.9772f, 0.0358f, 196.6f),
|
||||
1.0f to oklch(0.9886f, 0.0272f, 99.1f)
|
||||
)
|
||||
|
||||
internal val darkStops = arrayOf(
|
||||
0.4f to Color(0xFF040a24),
|
||||
0.72f to Color(0xFF3854ab),
|
||||
0.9f to Color(0xFFa8edf3),
|
||||
1.0f to Color(0xFFfff6e0)
|
||||
0.4f to oklch(0.1578f, 0.0609f, 267.3f),
|
||||
0.72f to oklch(0.4729f, 0.1574f, 267.3f),
|
||||
0.9f to oklch(0.9024f, 0.0760f, 202.8f),
|
||||
1.0f to oklch(0.9744f, 0.0370f, 88.4f)
|
||||
)
|
||||
|
||||
private fun Modifier.maxHeightByWidthRatio(ratio: Float) = layout { measurable, constraints ->
|
||||
|
||||
Reference in New Issue
Block a user