feat: custom Gradient theme with primary and secondary colors (#1309)

* feat: a custom Gradient theme with a primary and a secondary colour, picked with a wheel, sliders and a hex box

* Gradient theme: OK waits for a valid hex, deep blues are made paler, button text suits the worse gradient colour

* Gradient theme: nudge only the gradient colour that button text cannot read on

* fix: meet readable contrast across gradient themes

* fix: preserve contrast through gradient interpolation

---------

Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
This commit is contained in:
FX1922andxXJsonDeruloXx authored and GitHub committed 2026-10-22 10:26:53 -04:00
1 parent d2abaef2a3
commit 7482f797c7
43 files changed
+789 -5

No files matched your search

@@ -16,6 +16,7 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
com.droiddeck.launcher.ui.Themes.loadGradient(this)
// Helper processes must not initialize the main USB provider or guest graphics context.
val process = if (android.os.Build.VERSION.SDK_INT >= 28) Application.getProcessName()
else (getSystemService(ACTIVITY_SERVICE) as android.app.ActivityManager)
@@ -3418,7 +3418,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
private fun finishFlooded() {
if (isFinishing) return
val signal = com.droiddeck.launcher.ui.Themes.byId(SessionPrefs.theme(this)).signal
val signal = com.droiddeck.launcher.ui.Themes.current(this).signal
com.droiddeck.launcher.ui.QuitFlood.mark(signal.toArgb())
window.setBackgroundDrawable(android.graphics.drawable.ColorDrawable(signal.toArgb()))
quitFlooded = true
@@ -3449,7 +3449,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
private fun completeLeave() {
if (isFinishing) return
val signal = com.droiddeck.launcher.ui.Themes.byId(SessionPrefs.theme(this)).signal
val signal = com.droiddeck.launcher.ui.Themes.current(this).signal
com.droiddeck.launcher.ui.QuitFlood.mark(signal.toArgb())
window.setBackgroundDrawable(android.graphics.drawable.ColorDrawable(signal.toArgb()))
quitFlooded = true
@@ -759,6 +759,16 @@ object SessionPrefs {
prefs(context).edit().putString("theme", id).apply()
}
/** The custom Gradient theme's primary and secondary colours (ARGB). */
fun gradientColors(context: Context): Pair<Int, Int> = prefs(context).let {
it.getInt("gradientPrimary", com.droiddeck.launcher.ui.Themes.DEFAULT_PRIMARY) to
it.getInt("gradientSecondary", com.droiddeck.launcher.ui.Themes.DEFAULT_SECONDARY)
}
fun setGradientColors(context: Context, primary: Int, secondary: Int) {
prefs(context).edit().putInt("gradientPrimary", primary).putInt("gradientSecondary", secondary).apply()
}
private fun customResolution(context: Context, mode: String): Pair<Int, Int>? =
parseResolution(prefs(context).getString("customRes.$mode", null))
@@ -0,0 +1,141 @@
package com.droiddeck.launcher.ui
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.droiddeck.launcher.R
import kotlin.math.atan2
import kotlin.math.hypot
import kotlin.math.min
/** A small colour square; pressing it opens the colour picker. */
@Composable
internal fun ColorCube(color: Color, description: String, focus: FocusRequester? = null, onClick: () -> Unit) {
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src)
val shape = RoundedCornerShape(10.dp)
Box(
Modifier.then(if (focus != null) Modifier.focusRequester(focus) else Modifier).size(44.dp).clip(shape).background(color)
.border(1.dp, pal.line2, shape).glideBorder(hot, shape, pal.signal)
.hoverable(src).clickable(interactionSource = src, indication = androidx.compose.foundation.LocalIndication.current, role = Role.Button, onClick = onClick)
.controllerConfirm(onClick = onClick)
.semantics { contentDescription = description },
)
}
/** Colour wheel (touch), hue / saturation / brightness sliders (controller and touch) and a hex box for one colour. */
@Composable
internal fun ColorPickerDialog(title: String, initial: Int, onDismiss: () -> Unit, onApply: (Int) -> Unit) {
val shown = rememberShown(onDismiss)
val close = { shown.targetState = false }
val start = remember { ThemeColors.toHsv(initial) }
var hue by remember { mutableStateOf(start.h) }
var sat by remember { mutableStateOf(start.s) }
var value by remember { mutableStateOf(start.v) }
var hexText by remember { mutableStateOf(ThemeColors.hex(initial)) }
val current = ThemeColors.fromHsv(hue, sat, value)
val first = remember { FocusRequester() }
LaunchedEffect(Unit) { focusSoon { first } }
fun picked(h: Float = hue, s: Float = sat, v: Float = value) {
hue = h.coerceIn(0f, 360f); sat = s.coerceIn(0f, 1f); value = v.coerceIn(0f, 1f)
hexText = ThemeColors.hex(ThemeColors.fromHsv(hue, sat, value))
}
AppDialog(shown, close, "colorPicker", wide = false, maxWidth = 460.dp) {
DialogTitleRow(title, close)
Row(horizontalArrangement = Arrangement.spacedBy(14.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
ColorWheel(hue, sat, value) { h, s -> picked(h = h, s = s) }
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Swatch(Color(initial))
Swatch(Color(current))
}
}
ValueSlider(hue.toInt(), 0..360, step = 5, enabled = true, label = stringResource(R.string.theme_color_hue), format = { "$it°" },
modifier = Modifier.focusRequester(first)) { picked(h = it.toFloat()) }
ValueSlider((sat * 100).toInt(), 0..100, step = 2, enabled = true, label = stringResource(R.string.theme_color_saturation), format = { "$it%" }) { picked(s = it / 100f) }
ValueSlider((value * 100).toInt(), 0..100, step = 2, enabled = true, label = stringResource(R.string.theme_color_brightness), format = { "$it%" }) { picked(v = it / 100f) }
AdbTextField(
value = hexText,
onValueChange = { text ->
hexText = text
ThemeColors.parseHex(text)?.let { argb -> ThemeColors.toHsv(argb).let { hsv -> hue = hsv.h; sat = hsv.s; value = hsv.v } }
},
label = "", keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done, placeholder = stringResource(R.string.theme_color_hex),
onDone = { ThemeColors.parseHex(hexText)?.let { onApply(it); close() } },
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
SecondaryButton(stringResource(R.string.common_cancel), modifier = Modifier.weight(1f), onClick = close)
PrimaryButton(stringResource(R.string.common_ok), enabled = ThemeColors.parseHex(hexText) != null, modifier = Modifier.weight(1f)) {
ThemeColors.parseHex(hexText)?.let { onApply(it); close() }
}
}
}
}
@Composable
private fun Swatch(color: Color) {
val shape = RoundedCornerShape(10.dp)
Box(Modifier.size(48.dp).clip(shape).background(color).border(1.dp, LocalPalette.current.line2, shape))
}
/** Hue around the circle and saturation out from the middle, at full brightness; brightness has its own slider. */
@Composable
private fun ColorWheel(hue: Float, sat: Float, value: Float, onPick: (hue: Float, sat: Float) -> Unit) {
val rim = MaterialTheme.colorScheme.onBackground
val size = 168.dp
fun at(offset: Offset, side: Float) {
val center = side / 2f
val dx = offset.x - center
val dy = offset.y - center
val angle = Math.toDegrees(atan2(dy, dx).toDouble()).toFloat()
onPick((angle + 360f) % 360f, min(1f, hypot(dx, dy) / center))
}
Canvas(
Modifier.size(size)
.pointerInput(Unit) { detectTapGestures { at(it, this.size.width.toFloat()) } }
.pointerInput(Unit) { detectDragGestures { change, _ -> at(change.position, this.size.width.toFloat()); change.consume() } },
) {
val radius = this.size.minDimension / 2f
drawCircle(Brush.sweepGradient(List(7) { Color.hsv(it * 60f, 1f, 1f) }), radius)
drawCircle(Brush.radialGradient(listOf(Color.White, Color.Transparent), radius = radius), radius)
val theta = Math.toRadians(hue.toDouble())
val marker = Offset(radius + (kotlin.math.cos(theta) * sat * radius).toFloat(), radius + (kotlin.math.sin(theta) * sat * radius).toFloat())
drawCircle(Color.Black.copy(alpha = 0.55f), 9.dp.toPx(), marker)
drawCircle(rim, 7.dp.toPx(), marker)
drawCircle(Color.hsv(hue, sat, value), 5.dp.toPx(), marker)
}
}
@@ -359,6 +359,7 @@ internal fun SetupPanel(
}
}
}
if (s.theme == Themes.GRADIENT) GradientColorRows()
ChoiceRow(
host, "app-scale", stringResource(R.string.setup_app_scale), stringResource(R.string.setup_app_scale_hint),
com.droiddeck.launcher.core.AppUiPrefs.scales.map { percent ->
@@ -790,3 +791,21 @@ private fun LanguageRow(host: MenuHost, chosen: String, onPick: (String) -> Unit
}
}
}
/** The two colours of the Gradient theme, each a small square that opens the colour picker. */
@Composable
private fun GradientColorRows() {
val ctx = androidx.compose.ui.platform.LocalContext.current.applicationContext
val (primary, secondary) = Themes.gradientColors
var picking by remember { androidx.compose.runtime.mutableIntStateOf(0) }
val primaryLabel = stringResource(R.string.theme_gradient_primary)
val secondaryLabel = stringResource(R.string.theme_gradient_secondary)
SettingsRow(primaryLabel, null) { ColorCube(androidx.compose.ui.graphics.Color(primary), primaryLabel) { picking = 1 } }
SettingsRow(secondaryLabel, null) { ColorCube(androidx.compose.ui.graphics.Color(secondary), secondaryLabel) { picking = 2 } }
if (picking != 0) ColorPickerDialog(
if (picking == 1) primaryLabel else secondaryLabel, if (picking == 1) primary else secondary,
onDismiss = { picking = 0 },
) { argb ->
if (picking == 1) Themes.setGradient(ctx, argb, secondary) else Themes.setGradient(ctx, primary, argb)
}
}
@@ -6,6 +6,10 @@ import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import android.content.Context
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
@@ -29,6 +33,30 @@ object Themes {
const val PAPER = "paper"
const val ICON_BLUE = "blue"
const val ELECTRIC = "electric"
const val GRADIENT = "gradient"
const val DEFAULT_PRIMARY = 0xFF8B5CF6.toInt()
const val DEFAULT_SECONDARY = 0xFF22D3EE.toInt()
/** The two colours of the custom Gradient theme: primary, then secondary. Changing them restyles the app at once. */
var gradientColors by mutableStateOf(DEFAULT_PRIMARY to DEFAULT_SECONDARY)
private set
fun loadGradient(context: Context) {
gradientColors = SessionPrefs.gradientColors(context)
}
fun setGradient(context: Context, primary: Int, secondary: Int) {
SessionPrefs.setGradientColors(context, primary, secondary)
gradientColors = primary to secondary
}
/** The theme the person chose, with the custom colours read first; for code that is not in a composition. */
fun current(context: Context): Palette {
val id = SessionPrefs.theme(context)
if (id == GRADIENT) loadGradient(context)
return byId(id)
}
val all: List<Palette> = listOf(
Palette(
@@ -59,9 +87,13 @@ object Themes {
primary = Color(0xFF2E86FF), primary2 = Color(0xFF2684FF), onPrimary = Color(0xFF03111F), signal = Color(0xFF2E86FF),
good = Color(0xFF4CD37F),
),
ThemeColors.palette(DEFAULT_PRIMARY, DEFAULT_SECONDARY, R.string.theme_gradient, R.string.theme_gradient_detail),
)
fun byId(id: String): Palette = all.firstOrNull { it.id == id } ?: all[0]
fun byId(id: String): Palette {
if (id == GRADIENT) return ThemeColors.palette(gradientColors.first, gradientColors.second, R.string.theme_gradient, R.string.theme_gradient_detail)
return all.firstOrNull { it.id == id } ?: all[0]
}
}
val LocalPalette = staticCompositionLocalOf { Themes.byId(Themes.GRAPHITE) }
@@ -0,0 +1,157 @@
package com.droiddeck.launcher.ui
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.toArgb
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
/** Colour maths for the custom gradient theme: hue, saturation and brightness in, a readable palette out. */
internal object ThemeColors {
/** Hue 0..360, saturation and brightness 0..1. */
class Hsv(val h: Float, val s: Float, val v: Float)
fun toHsv(argb: Int): Hsv {
val r = (argb shr 16 and 0xFF) / 255f
val g = (argb shr 8 and 0xFF) / 255f
val b = (argb and 0xFF) / 255f
val top = max(r, max(g, b))
val delta = top - min(r, min(g, b))
val hue = when {
delta == 0f -> 0f
top == r -> 60f * (((g - b) / delta) % 6f)
top == g -> 60f * ((b - r) / delta + 2f)
else -> 60f * ((r - g) / delta + 4f)
}
return Hsv(if (hue < 0f) hue + 360f else hue, if (top == 0f) 0f else delta / top, top)
}
fun fromHsv(h: Float, s: Float, v: Float): Int =
Color.hsv(h.mod(360f), s.coerceIn(0f, 1f), v.coerceIn(0f, 1f)).toArgb()
fun hex(argb: Int): String = "#%06X".format(argb and 0xFFFFFF)
/** `#RRGGBB`, `RRGGBB` or `#RGB`; null for anything else. */
fun parseHex(text: String): Int? {
val digits = text.trim().removePrefix("#")
if (!digits.all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }) return null
val full = when (digits.length) {
3 -> digits.map { "$it$it" }.joinToString("")
6 -> digits
else -> return null
}
return 0xFF000000.toInt() or full.toInt(16)
}
private const val MIN_LUMINANCE = 0.10f
/** The colour, made brighter (then paler, for a deep blue that brightness alone cannot lift) when it is too dark to show on the dark background. */
fun readable(argb: Int): Int {
if (Color(argb).luminance() >= MIN_LUMINANCE) return argb or 0xFF000000.toInt()
val hsv = toHsv(argb)
var v = hsv.v
var s = hsv.s
var out = argb
while (v < 1f && Color(out).luminance() < MIN_LUMINANCE) {
v = min(1f, v + 0.04f)
out = fromHsv(hsv.h, s, v)
}
while (s > 0f && Color(out).luminance() < MIN_LUMINANCE) {
s = max(0f, s - 0.04f)
out = fromHsv(hsv.h, s, v)
}
return out
}
/** A dark neutral tinted with the primary's hue; plain greys stay grey. */
fun tint(primary: Int, saturation: Float, brightness: Float): Color {
val hsv = toHsv(primary)
val strength = (hsv.s / 0.35f).coerceIn(0f, 1f)
return Color(fromHsv(hsv.h, saturation * strength, brightness))
}
private val DARK_INK = Color(0xFF03111F)
private fun contrast(a: Color, b: Color): Float {
val x = a.luminance()
val y = b.luminance()
return (max(x, y) + 0.05f) / (min(x, y) + 0.05f)
}
/** Dark or white text, whichever reads better on the worse of the two gradient colours. */
internal fun inkFor(first: Int, second: Int): Color {
fun worst(ink: Color) = min(contrast(ink, Color(first)), contrast(ink, Color(second)))
return if (worst(DARK_INK) >= worst(Color.White)) DARK_INK else Color.White
}
private const val MIN_CONTRAST = 4.5f
/** The colour nudged just enough, lighter for dark text and darker for white text, to give [ink] a readable contrast on it. */
private fun fitTo(ink: Color, argb: Int): Int {
fun short(color: Int) = contrast(ink, Color(color)) < MIN_CONTRAST
if (!short(argb)) return argb
val hsv = toHsv(argb)
var v = hsv.v
var s = hsv.s
var out = argb
if (ink == DARK_INK) {
while (v < 1f && short(out)) { v = min(1f, v + 0.04f); out = fromHsv(hsv.h, s, v) }
while (s > 0f && short(out)) { s = max(0f, s - 0.04f); out = fromHsv(hsv.h, s, v) }
} else {
while (v > 0f && short(out)) { v = max(0f, v - 0.04f); out = fromHsv(hsv.h, s, v) }
}
return out
}
private fun gradientColor(first: Int, second: Int, fraction: Float): Int {
fun channel(shift: Int): Int {
val a = first shr shift and 0xFF
val b = second shr shift and 0xFF
return (a + (b - a) * fraction).roundToInt().coerceIn(0, 255)
}
return (0xFF shl 24) or (channel(16) shl 16) or (channel(8) shl 8) or channel(0)
}
private fun gradientFits(ink: Color, first: Int, second: Int): Boolean =
(0..32).all { step ->
val color = gradientColor(first, second, step / 32f)
contrast(ink, Color(color)) >= MIN_CONTRAST + 0.02f
}
private fun moveTowardContrast(ink: Color, argb: Int): Int {
val hsv = toHsv(argb)
return if (ink == DARK_INK) {
when {
hsv.v < 1f -> fromHsv(hsv.h, hsv.s, min(1f, hsv.v + 0.04f))
hsv.s > 0f -> fromHsv(hsv.h, max(0f, hsv.s - 0.04f), hsv.v)
else -> argb
}
} else {
fromHsv(hsv.h, hsv.s, max(0f, hsv.v - 0.04f))
}
}
fun palette(primary: Int, secondary: Int, label: Int, detail: Int): Palette {
val seen = readable(primary)
val seenSecond = readable(secondary)
val ink = inkFor(seen, seenSecond)
var first = fitTo(ink, seen)
var second = fitTo(ink, seenSecond)
while (!gradientFits(ink, first, second)) {
val nextFirst = moveTowardContrast(ink, first)
val nextSecond = moveTowardContrast(ink, second)
if (nextFirst == first && nextSecond == second) break
first = nextFirst
second = nextSecond
}
return Palette(
Themes.GRADIENT, label, detail,
background = tint(first, 0.50f, 0.045f), surface = tint(first, 0.45f, 0.075f), surfaceVariant = tint(first, 0.42f, 0.115f),
line = tint(first, 0.40f, 0.16f), line2 = tint(first, 0.36f, 0.22f),
onBackground = tint(first, 0.05f, 0.97f), onSurfaceVariant = tint(first, 0.16f, 0.68f),
primary = Color(first), primary2 = Color(second), onPrimary = ink, signal = Color(first),
good = Color(0xFF4CD37F),
)
}
}
@@ -16,7 +16,7 @@ import com.droiddeck.launcher.session.SessionPrefs
internal class TrackingRecoveryView(context: Context) : View(context) {
private var recovery: TrackingRecovery? = null
private var stereo = false
private var palette = Themes.byId(SessionPrefs.theme(context))
private var palette = Themes.current(context)
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var blocks = emptyList<StaticLayout>()
@@ -26,7 +26,7 @@ internal class TrackingRecoveryView(context: Context) : View(context) {
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_YES
}
fun update(state: TrackingRecovery?, stereo: Boolean, palette: Palette = Themes.byId(SessionPrefs.theme(context))) {
fun update(state: TrackingRecovery?, stereo: Boolean, palette: Palette = Themes.current(context)) {
if (recovery == state && this.stereo == stereo && this.palette == palette) return
recovery = state
this.stereo = stereo
+8
View File
@@ -1460,6 +1460,14 @@
<string name="theme_icon_blue_detail">خلفية سوداء ونص أبيض، والأزرق لكل عنصر محدَّد أو مُركَّز عليه أو مضغوط.</string>
<string name="theme_electric">كحلي كهربائي</string>
<string name="theme_electric_detail">أزرق أكثر سطوعًا، على أسطح تميل إلى الكحلي بدلاً من الرمادي.</string>
<string name="theme_gradient">تدرج لوني</string>
<string name="theme_gradient_detail">لونك الأساسي والثانوي، ممزوجان على الأزرار وعناصر التمييز.</string>
<string name="theme_gradient_primary">الأساسي</string>
<string name="theme_gradient_secondary">الثانوي</string>
<string name="theme_color_hue">درجة اللون</string>
<string name="theme_color_saturation">التشبع</string>
<string name="theme_color_brightness">السطوع</string>
<string name="theme_color_hex">لون سداسي (Hex)</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
@@ -1468,6 +1468,14 @@
<string name="theme_icon_blue_detail">Fondo negro, texto blanco y el azul para todo lo seleccionado, enfocado o presionado.</string>
<string name="theme_electric">Azul marino eléctrico</string>
<string name="theme_electric_detail">Un azul más vivo, sobre superficies que tiran a azul marino en vez de gris.</string>
<string name="theme_gradient">Degradado</string>
<string name="theme_gradient_detail">Tus propios colores primario y secundario, combinados en botones y resaltados.</string>
<string name="theme_gradient_primary">Primario</string>
<string name="theme_gradient_secondary">Secundario</string>
<string name="theme_color_hue">Tono</string>
<string name="theme_color_saturation">Saturación</string>
<string name="theme_color_brightness">Brillo</string>
<string name="theme_color_hex">Color hexadecimal</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Insert</string>
<string name="pckb_key_del">Supr</string>
+8
View File
@@ -1404,6 +1404,14 @@
<string name="theme_icon_blue_detail">Черен фон, бял текст, синьото за всичко избрано, на фокус или натиснато.</string>
<string name="theme_electric">Яркосиньо</string>
<string name="theme_electric_detail">По-яркото синьо, върху повърхности, по-близки до тъмносиньо, вместо сиво.</string>
<string name="theme_gradient">Градиент</string>
<string name="theme_gradient_detail">Вашите основен и вторичен цвят, смесени в бутоните и акцентите.</string>
<string name="theme_gradient_primary">Основен</string>
<string name="theme_gradient_secondary">Вторичен</string>
<string name="theme_color_hue">Нюанс</string>
<string name="theme_color_saturation">Наситеност</string>
<string name="theme_color_brightness">Яркост</string>
<string name="theme_color_hex">Hex цвят</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1432,6 +1432,14 @@
<string name="theme_icon_blue_detail">Černé pozadí, bílé písmo, modrá pro vše vybrané, zaměřené nebo stisknuté.</string>
<string name="theme_electric">Elektrická modř</string>
<string name="theme_electric_detail">Jasnější modrá na povrchách, které jsou spíše tmavomodré než šedé.</string>
<string name="theme_gradient">Přechod</string>
<string name="theme_gradient_detail">Vaše vlastní primární a sekundární barva, prolnuté na tlačítkách a zvýrazněních.</string>
<string name="theme_gradient_primary">Primární</string>
<string name="theme_gradient_secondary">Sekundární</string>
<string name="theme_color_hue">Odstín</string>
<string name="theme_color_saturation">Sytost</string>
<string name="theme_color_brightness">Jas</string>
<string name="theme_color_hex">Barva hex</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1404,6 +1404,14 @@
<string name="theme_icon_blue_detail">Sort baggrund, hvid skrift, blå til alt, der er valgt, fokuseret eller trykket.</string>
<string name="theme_electric">Elektrisk marineblå</string>
<string name="theme_electric_detail">Den blå er klarere, på overflader der trækker mod marineblåt i stedet for gråt.</string>
<string name="theme_gradient">Gradient</string>
<string name="theme_gradient_detail">Dine egne primære og sekundære farver, blandet på knapper og fremhævninger.</string>
<string name="theme_gradient_primary">Primær</string>
<string name="theme_gradient_secondary">Sekundær</string>
<string name="theme_color_hue">Farvetone</string>
<string name="theme_color_saturation">Mætning</string>
<string name="theme_color_brightness">Lysstyrke</string>
<string name="theme_color_hex">Hex-farve</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1393,6 +1393,14 @@
<string name="theme_icon_blue_detail">Schwarzer Grund, weiße Schrift, das Blau für alles Ausgewählte, Fokussierte oder Gedrückte.</string>
<string name="theme_electric">Elektrisches Marineblau</string>
<string name="theme_electric_detail">Das Blau heller, auf Flächen, die eher zu Marineblau als zu Grau tendieren.</string>
<string name="theme_gradient">Farbverlauf</string>
<string name="theme_gradient_detail">Eigene Primär- und Sekundärfarbe, als Verlauf auf Schaltflächen und Hervorhebungen.</string>
<string name="theme_gradient_primary">Primär</string>
<string name="theme_gradient_secondary">Sekundär</string>
<string name="theme_color_hue">Farbton</string>
<string name="theme_color_saturation">Sättigung</string>
<string name="theme_color_brightness">Helligkeit</string>
<string name="theme_color_hex">Hex-Farbe</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Einfg</string>
<string name="pckb_key_del">Entf</string>
+8
View File
@@ -1404,6 +1404,14 @@
<string name="theme_icon_blue_detail">Μαύρο φόντο, λευκά γράμματα, το μπλε για ό,τι είναι επιλεγμένο, εστιασμένο ή πατημένο.</string>
<string name="theme_electric">Ηλεκτρικό ναυτικό μπλε</string>
<string name="theme_electric_detail">Το μπλε πιο έντονο, σε επιφάνειες που τείνουν προς το ναυτικό μπλε αντί για γκρι.</string>
<string name="theme_gradient">Διαβάθμιση</string>
<string name="theme_gradient_detail">Δικά σας πρωτεύον και δευτερεύον χρώμα, σε μίξη στα κουμπιά και τις επισημάνσεις.</string>
<string name="theme_gradient_primary">Πρωτεύον</string>
<string name="theme_gradient_secondary">Δευτερεύον</string>
<string name="theme_color_hue">Απόχρωση</string>
<string name="theme_color_saturation">Κορεσμός</string>
<string name="theme_color_brightness">Φωτεινότητα</string>
<string name="theme_color_hex">Χρώμα hex</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1417,6 +1417,14 @@
<string name="theme_icon_blue_detail">Fondo negro, texto blanco y el azul para todo lo seleccionado, enfocado o pulsado.</string>
<string name="theme_electric">Azul marino eléctrico</string>
<string name="theme_electric_detail">Un azul más vivo, sobre superficies que tiran a azul marino en vez de gris.</string>
<string name="theme_gradient">Degradado</string>
<string name="theme_gradient_detail">Tus propios colores primario y secundario, combinados en botones y resaltados.</string>
<string name="theme_gradient_primary">Primario</string>
<string name="theme_gradient_secondary">Secundario</string>
<string name="theme_color_hue">Tono</string>
<string name="theme_color_saturation">Saturación</string>
<string name="theme_color_brightness">Brillo</string>
<string name="theme_color_hex">Color hexadecimal</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Insert</string>
<string name="pckb_key_del">Supr</string>
+8
View File
@@ -1404,6 +1404,14 @@
<string name="theme_icon_blue_detail">Musta pohja, valkoinen teksti, sininen kaikessa valitussa, kohdistetussa tai painetussa.</string>
<string name="theme_electric">Sähköinen laivastonsininen</string>
<string name="theme_electric_detail">Sininen kirkkaampana pinnoilla, jotka taittuvat harmaan sijaan laivastonsiniseen.</string>
<string name="theme_gradient">Liukuväri</string>
<string name="theme_gradient_detail">Omat ensisijainen ja toissijainen värisi, sulautettuina painikkeisiin ja korostuksiin.</string>
<string name="theme_gradient_primary">Ensisijainen</string>
<string name="theme_gradient_secondary">Toissijainen</string>
<string name="theme_color_hue">Sävy</string>
<string name="theme_color_saturation">Kylläisyys</string>
<string name="theme_color_brightness">Kirkkaus</string>
<string name="theme_color_hex">Hex-väri</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1417,6 +1417,14 @@
<string name="theme_icon_blue_detail">Fond noir, texte blanc, et le bleu pour tout élément sélectionné, ciblé ou pressé.</string>
<string name="theme_electric">Marine électrique</string>
<string name="theme_electric_detail">Un bleu plus vif, sur des surfaces qui tirent vers le marine plutôt que vers le gris.</string>
<string name="theme_gradient">Dégradé</string>
<string name="theme_gradient_detail">Vos propres couleurs primaire et secondaire, fondues sur les boutons et les éléments en surbrillance.</string>
<string name="theme_gradient_primary">Primaire</string>
<string name="theme_gradient_secondary">Secondaire</string>
<string name="theme_color_hue">Teinte</string>
<string name="theme_color_saturation">Saturation</string>
<string name="theme_color_brightness">Luminosité</string>
<string name="theme_color_hex">Couleur hex</string>
<string name="pckb_key_esc">Échap</string>
<string name="pckb_key_ins">Inser</string>
<string name="pckb_key_del">Suppr</string>
+8
View File
@@ -1404,6 +1404,14 @@
<string name="theme_icon_blue_detail">काली ज़मीन, सफ़ेद टाइप, चुने, फ़ोकस या दबाए गए हर चीज़ के लिए ब्लू रंग।</string>
<string name="theme_electric">इलेक्ट्रिक नेवी</string>
<string name="theme_electric_detail">ब्लू रंग ज़्यादा चमकीला, उन सतहों पर जो ग्रे के बजाय नेवी की ओर झुकती हैं।</string>
<string name="theme_gradient">ग्रेडिएंट</string>
<string name="theme_gradient_detail">आपके अपने प्राथमिक और द्वितीयक रंग, बटनों और हाइलाइट पर मिश्रित।</string>
<string name="theme_gradient_primary">प्राथमिक</string>
<string name="theme_gradient_secondary">द्वितीयक</string>
<string name="theme_color_hue">रंगत</string>
<string name="theme_color_saturation">संतृप्ति</string>
<string name="theme_color_brightness">चमक</string>
<string name="theme_color_hex">हेक्स रंग</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1404,6 +1404,14 @@
<string name="theme_icon_blue_detail">Fekete alap, fehér betűk, a kék minden kijelölt, fókuszált vagy lenyomott elemhez.</string>
<string name="theme_electric">Elektromos tengerészkék</string>
<string name="theme_electric_detail">Fényesebb kék, szürke helyett tengerészkék felületeken.</string>
<string name="theme_gradient">Színátmenet</string>
<string name="theme_gradient_detail">Saját elsődleges és másodlagos színed, átmenettel a gombokon és a kiemeléseken.</string>
<string name="theme_gradient_primary">Elsődleges</string>
<string name="theme_gradient_secondary">Másodlagos</string>
<string name="theme_color_hue">Színárnyalat</string>
<string name="theme_color_saturation">Telítettség</string>
<string name="theme_color_brightness">Fényerő</string>
<string name="theme_color_hex">Hex szín</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1390,6 +1390,14 @@
<string name="theme_icon_blue_detail">Dasar hitam, tipe putih, biru untuk semua yang dipilih, difokuskan, atau ditekan.</string>
<string name="theme_electric">Navy elektrik</string>
<string name="theme_electric_detail">Biru yang lebih cerah, pada permukaan yang condong ke navy, bukan abu-abu.</string>
<string name="theme_gradient">Gradasi</string>
<string name="theme_gradient_detail">Warna primer dan sekunder pilihan Anda, dipadukan pada tombol dan sorotan.</string>
<string name="theme_gradient_primary">Primer</string>
<string name="theme_gradient_secondary">Sekunder</string>
<string name="theme_color_hue">Rona</string>
<string name="theme_color_saturation">Saturasi</string>
<string name="theme_color_brightness">Kecerahan</string>
<string name="theme_color_hex">Warna hex</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1407,6 +1407,14 @@
<string name="theme_icon_blue_detail">Fondo nero, testo bianco, il blu per tutto ciò che è selezionato, attivo o premuto.</string>
<string name="theme_electric">Blu elettrico</string>
<string name="theme_electric_detail">Il blu più brillante, su superfici che tendono al blu navy invece che al grigio.</string>
<string name="theme_gradient">Gradiente</string>
<string name="theme_gradient_detail">I tuoi colori primario e secondario, sfumati su pulsanti ed evidenziazioni.</string>
<string name="theme_gradient_primary">Primario</string>
<string name="theme_gradient_secondary">Secondario</string>
<string name="theme_color_hue">Tonalità</string>
<string name="theme_color_saturation">Saturazione</string>
<string name="theme_color_brightness">Luminosità</string>
<string name="theme_color_hex">Colore esadecimale</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Canc</string>
+8
View File
@@ -1392,6 +1392,14 @@
<string name="theme_icon_blue_detail">黒の背景に白の文字、選択・フォーカス・押下時には青を使用します。</string>
<string name="theme_electric">エレクトリックネイビー</string>
<string name="theme_electric_detail">より明るい青を、グレーではなくネイビー寄りの画面に使用します。</string>
<string name="theme_gradient">グラデーション</string>
<string name="theme_gradient_detail">自分で選んだプライマリとセカンダリの色を、ボタンやハイライトにグラデーションで使います。</string>
<string name="theme_gradient_primary">プライマリ</string>
<string name="theme_gradient_secondary">セカンダリ</string>
<string name="theme_color_hue">色相</string>
<string name="theme_color_saturation">彩度</string>
<string name="theme_color_brightness">明度</string>
<string name="theme_color_hex">16進カラー</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1392,6 +1392,14 @@
<string name="theme_icon_blue_detail">검은색 배경에 흰색 글자, 선택·포커스·누름 상태에는 파란색을 사용합니다.</string>
<string name="theme_electric">일렉트릭 네이비</string>
<string name="theme_electric_detail">회색 대신 네이비 톤의 화면에 더 밝은 파란색을 사용합니다.</string>
<string name="theme_gradient">그라데이션</string>
<string name="theme_gradient_detail">직접 고른 기본색과 보조색이 버튼과 강조 표시에 섞여 적용됩니다.</string>
<string name="theme_gradient_primary">기본색</string>
<string name="theme_gradient_secondary">보조색</string>
<string name="theme_color_hue">색조</string>
<string name="theme_color_saturation">채도</string>
<string name="theme_color_brightness">명도</string>
<string name="theme_color_hex">16진수 색상</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1390,6 +1390,14 @@
<string name="theme_icon_blue_detail">Latar hitam, teks putih, biru untuk semua yang dipilih, difokus atau ditekan.</string>
<string name="theme_electric">Navy elektrik</string>
<string name="theme_electric_detail">Biru lebih cerah, pada permukaan yang lebih cenderung navy daripada kelabu.</string>
<string name="theme_gradient">Kecerunan</string>
<string name="theme_gradient_detail">Warna utama dan sekunder pilihan anda, diadun pada butang dan sorotan.</string>
<string name="theme_gradient_primary">Utama</string>
<string name="theme_gradient_secondary">Sekunder</string>
<string name="theme_color_hue">Rona</string>
<string name="theme_color_saturation">Ketepuan</string>
<string name="theme_color_brightness">Kecerahan</string>
<string name="theme_color_hex">Warna hex</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1404,6 +1404,14 @@
<string name="theme_icon_blue_detail">Svart bakgrunn, hvit skrift, det blå for alt som er valgt, fokusert eller trykket.</string>
<string name="theme_electric">Elektrisk marineblå</string>
<string name="theme_electric_detail">Det blå lysere, på flater som lener mer mot marineblått enn grått.</string>
<string name="theme_gradient">Gradient</string>
<string name="theme_gradient_detail">Dine egne primær- og sekundærfarger, blandet på knapper og uthevinger.</string>
<string name="theme_gradient_primary">Primær</string>
<string name="theme_gradient_secondary">Sekundær</string>
<string name="theme_color_hue">Fargetone</string>
<string name="theme_color_saturation">Metning</string>
<string name="theme_color_brightness">Lysstyrke</string>
<string name="theme_color_hex">Hex-farge</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1404,6 +1404,14 @@
<string name="theme_icon_blue_detail">Zwarte achtergrond, witte tekst, het blauw voor alles wat geselecteerd, gefocust of ingedrukt is.</string>
<string name="theme_electric">Elektrisch marineblauw</string>
<string name="theme_electric_detail">Het blauw feller, op vlakken die naar marineblauw neigen in plaats van grijs.</string>
<string name="theme_gradient">Kleurverloop</string>
<string name="theme_gradient_detail">Je eigen primaire en secundaire kleur, doorlopend op knoppen en accenten.</string>
<string name="theme_gradient_primary">Primair</string>
<string name="theme_gradient_secondary">Secundair</string>
<string name="theme_color_hue">Tint</string>
<string name="theme_color_saturation">Verzadiging</string>
<string name="theme_color_brightness">Helderheid</string>
<string name="theme_color_hex">Hex-kleur</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1432,6 +1432,14 @@
<string name="theme_icon_blue_detail">Czarne tło, biały tekst i niebieski dla wszystkiego, co zaznaczone, aktywne lub naciśnięte.</string>
<string name="theme_electric">Elektryczny granat</string>
<string name="theme_electric_detail">Jaśniejszy niebieski na powierzchniach wpadających w granat zamiast w szarość.</string>
<string name="theme_gradient">Gradient</string>
<string name="theme_gradient_detail">Własny kolor podstawowy i dodatkowy, płynnie przechodzące na przyciskach i wyróżnieniach.</string>
<string name="theme_gradient_primary">Podstawowy</string>
<string name="theme_gradient_secondary">Dodatkowy</string>
<string name="theme_color_hue">Odcień</string>
<string name="theme_color_saturation">Nasycenie</string>
<string name="theme_color_brightness">Jasność</string>
<string name="theme_color_hex">Kolor hex</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
@@ -1407,6 +1407,14 @@
<string name="theme_icon_blue_detail">Fundo preto, texto branco e o azul para tudo o que está selecionado, focado ou premido.</string>
<string name="theme_electric">Azul-marinho elétrico</string>
<string name="theme_electric_detail">O azul mais vivo, em superfícies que tendem para o azul-marinho em vez do cinzento.</string>
<string name="theme_gradient">Gradiente</string>
<string name="theme_gradient_detail">As suas próprias cores primária e secundária, combinadas nos botões e destaques.</string>
<string name="theme_gradient_primary">Primária</string>
<string name="theme_gradient_secondary">Secundária</string>
<string name="theme_color_hue">Matiz</string>
<string name="theme_color_saturation">Saturação</string>
<string name="theme_color_brightness">Brilho</string>
<string name="theme_color_hex">Cor hexadecimal</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1407,6 +1407,14 @@
<string name="theme_icon_blue_detail">Fundo preto, texto branco e o azul em tudo que estiver selecionado, em foco ou pressionado.</string>
<string name="theme_electric">Marinho elétrico</string>
<string name="theme_electric_detail">O azul mais vivo, sobre superfícies puxadas para o marinho em vez do cinza.</string>
<string name="theme_gradient">Gradiente</string>
<string name="theme_gradient_detail">Suas próprias cores primária e secundária, combinadas em botões e destaques.</string>
<string name="theme_gradient_primary">Primária</string>
<string name="theme_gradient_secondary">Secundária</string>
<string name="theme_color_hue">Matiz</string>
<string name="theme_color_saturation">Saturação</string>
<string name="theme_color_brightness">Brilho</string>
<string name="theme_color_hex">Cor hexadecimal</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1418,6 +1418,14 @@
<string name="theme_icon_blue_detail">Fundal negru, text alb, albastrul pentru tot ce este selectat, focalizat sau apăsat.</string>
<string name="theme_electric">Bleumarin electric</string>
<string name="theme_electric_detail">Albastrul mai strălucitor, pe suprafețe care tind spre bleumarin în loc de gri.</string>
<string name="theme_gradient">Gradient</string>
<string name="theme_gradient_detail">Culorile tale primară și secundară, combinate pe butoane și evidențieri.</string>
<string name="theme_gradient_primary">Primară</string>
<string name="theme_gradient_secondary">Secundară</string>
<string name="theme_color_hue">Nuanță</string>
<string name="theme_color_saturation">Saturație</string>
<string name="theme_color_brightness">Luminozitate</string>
<string name="theme_color_hex">Culoare hex</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1431,6 +1431,14 @@
<string name="theme_icon_blue_detail">Черный фон, белый текст, синий — для всего выбранного, активного или нажатого.</string>
<string name="theme_electric">Электрик</string>
<string name="theme_electric_detail">Более яркий синий на поверхностях темно-синего, а не серого оттенка.</string>
<string name="theme_gradient">Градиент</string>
<string name="theme_gradient_detail">Ваши основной и дополнительный цвета, плавно переходящие на кнопках и выделениях.</string>
<string name="theme_gradient_primary">Основной</string>
<string name="theme_gradient_secondary">Дополнительный</string>
<string name="theme_color_hue">Оттенок</string>
<string name="theme_color_saturation">Насыщенность</string>
<string name="theme_color_brightness">Яркость</string>
<string name="theme_color_hex">Цвет hex</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1404,6 +1404,14 @@
<string name="theme_icon_blue_detail">Svart bakgrund, vit text, den blå färgen för allt som är markerat, i fokus eller nedtryckt.</string>
<string name="theme_electric">Elektrisk marinblå</string>
<string name="theme_electric_detail">Den blå färgen klarare, på ytor som lutar åt marinblått i stället för grått.</string>
<string name="theme_gradient">Gradient</string>
<string name="theme_gradient_detail">Dina egna primära och sekundära färger, blandade på knappar och markeringar.</string>
<string name="theme_gradient_primary">Primär</string>
<string name="theme_gradient_secondary">Sekundär</string>
<string name="theme_color_hue">Nyans</string>
<string name="theme_color_saturation">Mättnad</string>
<string name="theme_color_brightness">Ljusstyrka</string>
<string name="theme_color_hex">Hex-färg</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1390,6 +1390,14 @@
<string name="theme_icon_blue_detail">พื้นดำ ตัวอักษรขาว สีฟ้าสำหรับทุกสิ่งที่ถูกเลือก โฟกัส หรือกด</string>
<string name="theme_electric">กรมท่าไฟฟ้า</string>
<string name="theme_electric_detail">สีฟ้าสว่างขึ้น บนพื้นผิวที่เอียงไปทางกรมท่ามากกว่าเทา</string>
<string name="theme_gradient">ไล่เฉดสี</string>
<string name="theme_gradient_detail">สีหลักและสีรองที่คุณเลือกเอง ผสมกันบนปุ่มและส่วนที่เน้น</string>
<string name="theme_gradient_primary">สีหลัก</string>
<string name="theme_gradient_secondary">สีรอง</string>
<string name="theme_color_hue">เฉดสี</string>
<string name="theme_color_saturation">ความอิ่มตัว</string>
<string name="theme_color_brightness">ความสว่าง</string>
<string name="theme_color_hex">สีเลขฐานสิบหก</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1393,6 +1393,14 @@
<string name="theme_icon_blue_detail">Siyah zemin, beyaz yazı; seçilen, odaklanan veya basılan her şey için mavi.</string>
<string name="theme_electric">Elektrik laciverti</string>
<string name="theme_electric_detail">Daha parlak mavi; gri yerine laciverte çalan yüzeylerde.</string>
<string name="theme_gradient">Geçiş</string>
<string name="theme_gradient_detail">Kendi birincil ve ikincil renklerin, düğmelerde ve vurgularda harmanlanır.</string>
<string name="theme_gradient_primary">Birincil</string>
<string name="theme_gradient_secondary">İkincil</string>
<string name="theme_color_hue">Ton</string>
<string name="theme_color_saturation">Doygunluk</string>
<string name="theme_color_brightness">Parlaklık</string>
<string name="theme_color_hex">Hex rengi</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1432,6 +1432,14 @@
<string name="theme_icon_blue_detail">Чорне тло, білий текст і синій для всього вибраного, у фокусі чи натиснутого.</string>
<string name="theme_electric">Електричний темно-синій</string>
<string name="theme_electric_detail">Яскравіший синій на поверхнях із темно-синім відтінком замість сірого.</string>
<string name="theme_gradient">Градієнт</string>
<string name="theme_gradient_detail">Ваші основний і додатковий кольори, що плавно переходять на кнопках і виділеннях.</string>
<string name="theme_gradient_primary">Основний</string>
<string name="theme_gradient_secondary">Додатковий</string>
<string name="theme_color_hue">Відтінок</string>
<string name="theme_color_saturation">Насиченість</string>
<string name="theme_color_brightness">Яскравість</string>
<string name="theme_color_hex">Колір hex</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1390,6 +1390,14 @@
<string name="theme_icon_blue_detail">Nền đen, chữ trắng, xanh cho mọi thứ được chọn, lấy nét hoặc nhấn.</string>
<string name="theme_electric">Xanh điện navy</string>
<string name="theme_electric_detail">Màu xanh sáng hơn, trên các bề mặt nghiêng về navy thay vì xám.</string>
<string name="theme_gradient">Chuyển màu</string>
<string name="theme_gradient_detail">Màu chính và màu phụ do bạn chọn, hòa trộn trên các nút và phần nổi bật.</string>
<string name="theme_gradient_primary">Chính</string>
<string name="theme_gradient_secondary">Phụ</string>
<string name="theme_color_hue">Sắc độ</string>
<string name="theme_color_saturation">Độ bão hòa</string>
<string name="theme_color_brightness">Độ sáng</string>
<string name="theme_color_hex">Màu hex</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
@@ -1468,6 +1468,14 @@
<string name="theme_icon_blue_detail">黑色背景、白色文字,选中、聚焦或按下的元素使用蓝色。</string>
<string name="theme_electric">电光藏青</string>
<string name="theme_electric_detail">更明亮的蓝色,配以偏藏青而非灰色的界面。</string>
<string name="theme_gradient">渐变</string>
<string name="theme_gradient_detail">你自己选择的主色和辅色,在按钮和高亮处渐变呈现。</string>
<string name="theme_gradient_primary">主色</string>
<string name="theme_gradient_secondary">辅色</string>
<string name="theme_color_hue">色相</string>
<string name="theme_color_saturation">饱和度</string>
<string name="theme_color_brightness">亮度</string>
<string name="theme_color_hex">十六进制颜色</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
@@ -1392,6 +1392,14 @@
<string name="theme_icon_blue_detail">黑色背景、白色文字,所有已選取、焦點所在或按下的項目都使用藍色。</string>
<string name="theme_electric">電光海軍藍</string>
<string name="theme_electric_detail">更明亮的藍色,搭配偏海軍藍而非灰色的介面。</string>
<string name="theme_gradient">漸層</string>
<string name="theme_gradient_detail">你自行選擇的主色與輔色,在按鈕與強調處以漸層呈現。</string>
<string name="theme_gradient_primary">主色</string>
<string name="theme_gradient_secondary">輔色</string>
<string name="theme_color_hue">色相</string>
<string name="theme_color_saturation">飽和度</string>
<string name="theme_color_brightness">亮度</string>
<string name="theme_color_hex">十六進位色彩</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
@@ -1392,6 +1392,14 @@
<string name="theme_icon_blue_detail">黑色背景、白色文字,選取、聚焦或按下的項目使用藍色。</string>
<string name="theme_electric">電光藏青</string>
<string name="theme_electric_detail">更亮的藍色,搭配偏藏青而非灰色的介面。</string>
<string name="theme_gradient">漸層</string>
<string name="theme_gradient_detail">你自行選擇的主色與輔色,在按鈕與強調處以漸層呈現。</string>
<string name="theme_gradient_primary">主色</string>
<string name="theme_gradient_secondary">輔色</string>
<string name="theme_color_hue">色相</string>
<string name="theme_color_saturation">飽和度</string>
<string name="theme_color_brightness">亮度</string>
<string name="theme_color_hex">十六進位色彩</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
+8
View File
@@ -1459,6 +1459,14 @@
<string name="theme_icon_blue_detail">Black ground, white type, the blue for everything selected, focused or pressed.</string>
<string name="theme_electric">Electric navy</string>
<string name="theme_electric_detail">The blue brighter, on surfaces that lean navy instead of grey.</string>
<string name="theme_gradient">Gradient</string>
<string name="theme_gradient_detail">Your own primary and secondary colors, blended on buttons and highlights.</string>
<string name="theme_gradient_primary">Primary</string>
<string name="theme_gradient_secondary">Secondary</string>
<string name="theme_color_hue">Hue</string>
<string name="theme_color_saturation">Saturation</string>
<string name="theme_color_brightness">Brightness</string>
<string name="theme_color_hex">Hex color</string>
<string name="pckb_key_esc">Esc</string>
<string name="pckb_key_ins">Ins</string>
<string name="pckb_key_del">Del</string>
@@ -0,0 +1,135 @@
package com.droiddeck.launcher.ui
import android.content.Context
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.toArgb
import com.droiddeck.launcher.session.SessionPrefs
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [33])
class ThemeColorsTest {
private val app get() = RuntimeEnvironment.getApplication()
@Before fun clear() {
app.getSharedPreferences("session", Context.MODE_PRIVATE).edit().clear().commit()
}
private fun near(expected: Int, actual: Int) {
for (shift in listOf(16, 8, 0)) assertTrue("${ThemeColors.hex(expected)} vs ${ThemeColors.hex(actual)}", Math.abs((expected shr shift and 0xFF) - (actual shr shift and 0xFF)) <= 1)
}
@Test fun hueSaturationAndBrightnessComeBackAsTheSameColour() {
for (argb in listOf(0xFF8B5CF6.toInt(), 0xFF22D3EE.toInt(), 0xFFFF0000.toInt(), 0xFF336699.toInt(), 0xFF808080.toInt(), 0xFF000000.toInt(), 0xFFFFFFFF.toInt())) {
val hsv = ThemeColors.toHsv(argb)
near(argb, ThemeColors.fromHsv(hsv.h, hsv.s, hsv.v))
}
}
@Test fun hexIsReadWithOrWithoutTheHashAndInShortForm() {
assertEquals(0xFF8B5CF6.toInt(), ThemeColors.parseHex("#8B5CF6"))
assertEquals(0xFF8B5CF6.toInt(), ThemeColors.parseHex("8b5cf6"))
assertEquals(0xFFFF00AA.toInt(), ThemeColors.parseHex(" #f0a "))
assertEquals("#8B5CF6", ThemeColors.hex(0xFF8B5CF6.toInt()))
for (bad in listOf("", "#", "#12", "#1234", "#1234567", "zzzzzz", "#12345g")) assertNull(bad, ThemeColors.parseHex(bad))
}
@Test fun aTooDarkColourIsLiftedAndABrightOneIsLeftAlone() {
val dark = ThemeColors.readable(0xFF050505.toInt())
assertTrue(Color(dark).luminance() >= 0.10f)
assertEquals(0xFF8B5CF6.toInt(), ThemeColors.readable(0xFF8B5CF6.toInt()))
}
@Test fun aDeepBlueThatBrightnessAloneCannotLiftIsMadePaler() {
assertTrue(Color(ThemeColors.readable(0xFF0000FF.toInt())).luminance() >= 0.10f)
assertTrue(Color(ThemeColors.readable(0xFF000000.toInt())).luminance() >= 0.10f)
}
@Test fun theTextColourSuitsTheWorseOfTheTwoGradientColours() {
assertEquals(Color(0xFF03111F), ThemeColors.inkFor(0xFFFFEB3B.toInt(), 0xFF3B4FA8.toInt()))
assertEquals(Color.White, ThemeColors.inkFor(0xFF3B4FA8.toInt(), 0xFF5B2FA8.toInt()))
assertEquals(Color(0xFF03111F), ThemeColors.inkFor(0xFFFFFFFF.toInt(), 0xFFFFEB3B.toInt()))
}
private fun contrast(a: Color, b: Color) = (maxOf(a.luminance(), b.luminance()) + 0.05f) / (minOf(a.luminance(), b.luminance()) + 0.05f)
@Test fun buttonTextIsReadableOnBothGradientColoursForAnyPair() {
val picks = listOf(0xFFFFEB3B, 0xFF1E3A8A, 0xFF0000FF, 0xFFFF0000, 0xFF00FF00, 0xFF808080, 0xFFFFFFFF, 0xFF000000, 0xFF8B5CF6, 0xFF22D3EE, 0xFFFF5722, 0xFF3F51B5)
for (a in picks) for (b in picks) {
val p = ThemeColors.palette(a.toInt(), b.toInt(), 0, 0)
val first = p.primary.toArgb()
val second = p.primary2.toArgb()
for (step in 0..100) {
val fraction = step / 100f
val color = Color(
0xFF000000.toInt() or
(channel(first, 16, second, fraction) shl 16) or
(channel(first, 8, second, fraction) shl 8) or
channel(first, 0, second, fraction),
)
assertTrue(
"${ThemeColors.hex(first)} ${ThemeColors.hex(second)} at $fraction",
contrast(p.onPrimary, color) >= 4.5f,
)
}
}
}
private fun channel(first: Int, shift: Int, second: Int, fraction: Float): Int {
val start = first shr shift and 0xFF
val end = second shr shift and 0xFF
return (start + (end - start) * fraction).toInt().coerceIn(0, 255)
}
@Test fun onlyTheStopThatFailsIsNudged() {
val p = ThemeColors.palette(0xFFFFEB3B.toInt(), 0xFF1E3A8A.toInt(), 0, 0)
assertEquals(Color(0xFFFFEB3B), p.primary)
assertTrue(p.primary2 != Color(0xFF1E3A8A))
assertEquals(Color(0xFF03111F), p.onPrimary)
}
@Test fun thePaletteKeepsTheTwoColoursAndStaysDarkAndReadable() {
val p = ThemeColors.palette(0xFF8B5CF6.toInt(), 0xFF22D3EE.toInt(), 0, 0)
assertEquals(p.primary, p.signal)
assertEquals(Color(0xFF22D3EE), p.primary2)
assertTrue(contrast(p.onPrimary, p.primary) >= 4.5f)
assertTrue(contrast(p.onPrimary, p.primary2) >= 4.5f)
assertTrue(p.background.luminance() < p.surface.luminance())
assertTrue(p.surface.luminance() < p.surfaceVariant.luminance())
assertTrue(p.background.luminance() < 0.02f)
assertTrue(p.onBackground.luminance() > 0.8f)
assertEquals(Color(0xFF03111F), ThemeColors.palette(0xFFFFEB3B.toInt(), 0xFFFFFFFF.toInt(), 0, 0).onPrimary)
assertEquals(Color.White, ThemeColors.palette(0xFF1E40AF.toInt(), 0xFF1D4ED8.toInt(), 0, 0).onPrimary)
}
@Test fun aGreyPrimaryGivesGreyNeutrals() {
val p = ThemeColors.palette(0xFFB0B0B0.toInt(), 0xFFFFFFFF.toInt(), 0, 0)
assertEquals(p.background.red, p.background.blue, 0.002f)
}
@Test fun theGradientThemeFollowsTheSavedColoursAndComesBackAfterARestart() {
Themes.setGradient(app, 0xFFFF5722.toInt(), 0xFF3F51B5.toInt())
val live = Themes.byId(Themes.GRADIENT)
assertEquals(0xFFFF5722.toInt() to 0xFF3F51B5.toInt(), SessionPrefs.gradientColors(app))
assertTrue(contrast(live.onPrimary, live.primary) >= 4.5f)
assertTrue(contrast(live.onPrimary, live.primary2) >= 4.5f)
SessionPrefs.setTheme(app, Themes.GRADIENT)
Themes.setGradient(app, Themes.DEFAULT_PRIMARY, Themes.DEFAULT_SECONDARY)
SessionPrefs.setGradientColors(app, 0xFF00FF00.toInt(), 0xFF0000FF.toInt())
assertEquals(Color(0xFF00FF00), Themes.current(app).primary)
}
@Test fun theGradientIsAChoiceInTheThemeList() {
assertTrue(Themes.all.any { it.id == Themes.GRADIENT })
assertEquals(Themes.GRAPHITE, Themes.byId("nonsense").id)
}
}
+25
View File
@@ -0,0 +1,25 @@
# Themes
A theme is a `Palette` (`ui/Theme.kt`): a dark background scale (`background`, `surface`,
`surfaceVariant`, `line`, `line2`), two text colours, and the accent: `primary` / `signal`
(flat accent, also Material's primary), `primary2` (the darker or second accent) and
`onPrimary`. `PrimaryButton` and the launch flood already draw a gradient from `primary` to
`primary2`. The choice is stored as an id (`SessionPrefs.theme`) and picked in Setup › Launcher.
## Gradient theme
`Themes.GRADIENT` is the one theme whose colours the person picks: a primary and a secondary,
kept in `SessionPrefs.gradientColors` and in `Themes.gradientColors`, a Compose state, so a change
restyles the whole app at once. `App.onCreate` loads the saved colours; code outside a composition
uses `Themes.current(context)`.
`ThemeColors.palette` builds the full palette from the two colours: primary becomes
`primary` / `signal`, secondary becomes `primary2`, the background scale is a dark neutral tinted with
the primary's hue (plain grey for a grey primary), and `onPrimary` is dark or white by the
luminance of the pair. A colour too dark to show on the dark background is lifted
(`ThemeColors.readable`) rather than refused.
Setup shows Primary and Secondary under the theme when Gradient is chosen, each a small colour
square. It opens `ColorPickerDialog`: a wheel (hue and saturation, touch), hue / saturation /
brightness sliders (they work with a controller) and a hex box, with the old and new colour side by
side. OK applies it.