Add settings for variable font axes in typography scheme

This commit is contained in:
MM20
2026-01-31 23:21:55 +01:00
parent 77bcd806c7
commit 24406f6aae
7 changed files with 173 additions and 52 deletions

View File

@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
@@ -29,6 +30,7 @@ import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LeadingIconTab import androidx.compose.material3.LeadingIconTab
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedIconToggleButton import androidx.compose.material3.OutlinedIconToggleButton
@@ -46,6 +48,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -72,6 +75,7 @@ import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.component.preferences.Preference import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SliderPreference
import de.mm20.launcher2.ui.theme.typography.fontFamilyOf import de.mm20.launcher2.ui.theme.typography.fontFamilyOf
import de.mm20.launcher2.ui.theme.typography.typographyOf import de.mm20.launcher2.ui.theme.typography.typographyOf
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -745,24 +749,60 @@ private fun FontPreference(
val fontManager = FontManager(context) val fontManager = FontManager(context)
var showDialog by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(false) }
var showFontSettings by remember { mutableStateOf(false) }
Preference( val fontVariantAxes = remember(value) {
title = title, if (value is ThemeFontFamily.VariableFontFamily) {
summary = getFontName(context, value), fontManager.getFontSettings(value)
icon = { } else {
Text( emptyList()
text = preview.ExtraShort, }
style = TextStyle( }
fontFamily = remember(value) { fontFamilyOf(context, value) },
fontWeight = FontWeight.Normal, Row(
color = MaterialTheme.colorScheme.primary, verticalAlignment = Alignment.CenterVertically,
fontSize = 24.sp, modifier = Modifier.background(
textAlign = TextAlign.Center, MaterialTheme.colorScheme.surface,
) MaterialTheme.shapes.extraSmall
)
) {
Box(
modifier = Modifier.weight(1f)
) {
Preference(
title = title,
summary = getFontName(context, value),
icon = {
Text(
text = preview.ExtraShort,
style = TextStyle(
fontFamily = remember(value) { fontFamilyOf(context, value) },
fontWeight = FontWeight.Normal,
color = MaterialTheme.colorScheme.primary,
fontSize = 24.sp,
textAlign = TextAlign.Center,
)
)
},
onClick = { showDialog = true },
containerColor = Color.Transparent,
) )
}, }
onClick = { showDialog = true }, if (fontVariantAxes.isNotEmpty()) {
) Box(
modifier = Modifier
.height(36.dp)
.width(1.dp)
.background(MaterialTheme.colorScheme.outlineVariant)
)
IconButton(
modifier = Modifier.padding(horizontal = 12.dp),
onClick = { showFontSettings = true }
) {
Icon(painterResource(R.drawable.tune_24px), null)
}
}
}
DismissableBottomSheet( DismissableBottomSheet(
expanded = showDialog, expanded = showDialog,
@@ -774,7 +814,8 @@ private fun FontPreference(
start = 16.dp, start = 16.dp,
top = 16.dp, top = 16.dp,
end = 16.dp, end = 16.dp,
bottom = 16.dp + WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding(), bottom = 16.dp + WindowInsets.navigationBars.asPaddingValues()
.calculateBottomPadding(),
), ),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
@@ -816,6 +857,59 @@ private fun FontPreference(
} }
} }
} }
if (value is ThemeFontFamily.VariableFontFamily) {
DismissableBottomSheet(
expanded = showFontSettings,
onDismissRequest = {
showFontSettings = false
}
) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
.navigationBarsPadding()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
.heightIn(min = 200.dp),
contentAlignment = Alignment.Center,
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = preview.TwoLines,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.headlineMedium,
fontFamily = fontFamilyOf(context, value)
)
}
PreferenceCategory {
for (axis in fontVariantAxes) {
SliderPreference(
title = axis.label,
value = value.settings.get(axis.name) ?: axis.defaultValue,
onValueChanged = {
val map = value.settings.toMutableMap()
map[axis.name] = it
if (value is ThemeFontFamily.LauncherDefault) {
onValueChange(value.copy(settings = map))
}
},
min = axis.range.start,
max = axis.range.endInclusive,
step = axis.step,
)
}
}
}
}
}
} }
@Composable @Composable
@@ -860,7 +954,6 @@ private fun FontPickerCategory(
private fun getFontName(context: Context, fontFamily: ThemeFontFamily?): String { private fun getFontName(context: Context, fontFamily: ThemeFontFamily?): String {
return when (fontFamily) { return when (fontFamily) {
is ThemeFontFamily.LauncherDefault -> "Google Sans Flex" is ThemeFontFamily.LauncherDefault -> "Google Sans Flex"
is ThemeFontFamily.LauncherDefaultRound -> "Google Sans Flex (Rounded)"
is ThemeFontFamily.DeviceHeadline -> context.getString(R.string.font_name_device_headline) is ThemeFontFamily.DeviceHeadline -> context.getString(R.string.font_name_device_headline)
is ThemeFontFamily.DeviceBody -> context.getString(R.string.font_name_device_body) is ThemeFontFamily.DeviceBody -> context.getString(R.string.font_name_device_body)
is ThemeFontFamily.System -> fontFamily.name is ThemeFontFamily.System -> fontFamily.name

View File

@@ -13,7 +13,6 @@ import androidx.compose.ui.unit.sp
import de.mm20.launcher2.themes.typography.DefaultEmphasizedTextStyles import de.mm20.launcher2.themes.typography.DefaultEmphasizedTextStyles
import de.mm20.launcher2.themes.typography.DefaultTextStyles import de.mm20.launcher2.themes.typography.DefaultTextStyles
import de.mm20.launcher2.ui.theme.typography.fontfamily.GoogleSansFlex import de.mm20.launcher2.ui.theme.typography.fontfamily.GoogleSansFlex
import de.mm20.launcher2.ui.theme.typography.fontfamily.GoogleSansFlexRound
import de.mm20.launcher2.ui.theme.typography.fontfamily.getDeviceBodyFontFamily import de.mm20.launcher2.ui.theme.typography.fontfamily.getDeviceBodyFontFamily
import de.mm20.launcher2.ui.theme.typography.fontfamily.getDeviceHeadlineFontFamily import de.mm20.launcher2.ui.theme.typography.fontfamily.getDeviceHeadlineFontFamily
import de.mm20.launcher2.themes.typography.FontFamily as ThemeFontFamily import de.mm20.launcher2.themes.typography.FontFamily as ThemeFontFamily
@@ -317,8 +316,7 @@ fun fontFamilyOf(
fontFamily: ThemeFontFamily? fontFamily: ThemeFontFamily?
): FontFamily { ): FontFamily {
return when (fontFamily) { return when (fontFamily) {
is ThemeFontFamily.LauncherDefault -> GoogleSansFlex is ThemeFontFamily.LauncherDefault -> GoogleSansFlex(fontFamily.settings)
is ThemeFontFamily.LauncherDefaultRound -> GoogleSansFlexRound
is ThemeFontFamily.DeviceHeadline -> getDeviceHeadlineFontFamily(context) is ThemeFontFamily.DeviceHeadline -> getDeviceHeadlineFontFamily(context)
is ThemeFontFamily.DeviceBody -> getDeviceBodyFontFamily(context) is ThemeFontFamily.DeviceBody -> getDeviceBodyFontFamily(context)
is ThemeFontFamily.SansSerif -> FontFamily.SansSerif is ThemeFontFamily.SansSerif -> FontFamily.SansSerif

View File

@@ -1,16 +1,8 @@
package de.mm20.launcher2.ui.theme.typography.fontfamily package de.mm20.launcher2.ui.theme.typography.fontfamily
import androidx.compose.ui.text.font.FontVariation
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
fun GoogleSansFlex(settings: Map<String, Float>) = createVariableFontFamily(
val GoogleSansFlex = createVariableFontFamily(
R.font.google_sans_flex, R.font.google_sans_flex,
FontVariation.roundness(0f) settings
)
val GoogleSansFlexRound = createVariableFontFamily(
R.font.google_sans_flex,
FontVariation.roundness( 100f)
) )

View File

@@ -1,17 +1,35 @@
package de.mm20.launcher2.ui.theme.typography.fontfamily package de.mm20.launcher2.ui.theme.typography.fontfamily
import androidx.annotation.FontRes import androidx.annotation.FontRes
import androidx.collection.LruCache
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontVariation import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import java.io.File
internal fun FontVariation.roundness(roundness: Float): FontVariation.Setting {
return Setting("ROND", roundness) private val fontCache = LruCache<String, FontFamily>(20)
internal fun createVariableFontFamily(@FontRes resId: Int, settings: Map<String, Float>): FontFamily {
val cacheKey = "0.$resId/${settings.entries.joinToString(separator = ",") { "${it.key}=${it.value}" }}"
val cacheHit = fontCache.get(cacheKey)
if (cacheHit != null) {
return cacheHit
}
val family = createVariableFontFamily(
resId, *settings.map { FontVariation.Setting(it.key, it.value) }.toTypedArray()
)
fontCache.put(cacheKey, family)
return family
} }
internal fun createVariableFontFamily(@FontRes resId: Int, vararg settings: FontVariation.Setting): FontFamily { private fun createVariableFontFamily(@FontRes resId: Int, vararg settings: FontVariation.Setting): FontFamily {
val fonts = mutableListOf<Font>() val fonts = mutableListOf<Font>()
for (weight in 100..900 step 100) { for (weight in 100..900 step 100) {
fonts += Font( fonts += Font(

View File

@@ -1,11 +1,6 @@
package de.mm20.launcher2.themes.typography package de.mm20.launcher2.themes.typography
import android.content.Context import android.content.Context
import android.graphics.Typeface
import android.graphics.fonts.SystemFonts
import android.os.Build
import android.util.Log
import androidx.core.graphics.TypefaceCompat
data class FontList( data class FontList(
val builtIn: List<FontFamily>, val builtIn: List<FontFamily>,
@@ -14,6 +9,14 @@ data class FontList(
val system: List<FontFamily>, val system: List<FontFamily>,
) )
data class VariableFontAxis(
val name: String,
val label: String,
val range: ClosedFloatingPointRange<Float>,
val step: Float,
val defaultValue: Float,
)
class FontManager( class FontManager(
private val context: Context private val context: Context
) { ) {
@@ -24,12 +27,12 @@ class FontManager(
.getIdentifier("config_bodyFontFamily", "string", "android") .getIdentifier("config_bodyFontFamily", "string", "android")
val deviceHeadlineExists = deviceHeadlineResId != 0 && val deviceHeadlineExists = deviceHeadlineResId != 0 &&
context.resources.getString(deviceHeadlineResId).isNotBlank() context.resources.getString(deviceHeadlineResId).isNotBlank()
val deviceBodyExists = deviceBodyResId != 0 && val deviceBodyExists = deviceBodyResId != 0 &&
context.resources.getString(deviceBodyResId).isNotBlank() context.resources.getString(deviceBodyResId).isNotBlank()
return FontList( return FontList(
builtIn = listOf(FontFamily.LauncherDefault, FontFamily.LauncherDefaultRound), builtIn = listOf(FontFamily.LauncherDefault()),
generic = listOf( generic = listOf(
FontFamily.SansSerif, FontFamily.SansSerif,
FontFamily.Serif, FontFamily.Serif,
@@ -42,4 +45,16 @@ class FontManager(
system = emptyList(), system = emptyList(),
) )
} }
fun getFontSettings(font: FontFamily.VariableFontFamily): List<VariableFontAxis> {
if (font is FontFamily.LauncherDefault) {
return listOf(
VariableFontAxis("wdth", "Width", 25f..151f, 1f, 100f),
VariableFontAxis("ROND", "Roundness", 0f..100f, 1f, 0f),
VariableFontAxis("GRAD", "Grade", 0f..100f, 1f, 0f),
)
}
return emptyList()
}
} }

View File

@@ -181,13 +181,18 @@ data class TextStyle<out W : FontWeight?>(
@Serializable @Serializable
sealed interface FontFamily { sealed interface FontFamily {
@Serializable
@SerialName("launcher_default") sealed interface VariableFontFamily {
data object LauncherDefault : FontFamily val settings: Map<String, Float>
get() = emptyMap()
}
@Serializable @Serializable
@SerialName("launcher_default_round") @SerialName("launcher_default")
data object LauncherDefaultRound : FontFamily data class LauncherDefault(
override val settings: Map<String, Float> = emptyMap()
) : FontFamily, VariableFontFamily
@Serializable @Serializable
@SerialName("device_headline") @SerialName("device_headline")

View File

@@ -78,8 +78,8 @@ class TypographyRepository(
builtIn = true, builtIn = true,
name = "Google Sans", name = "Google Sans",
fonts = mapOf( fonts = mapOf(
"brand" to FontFamily.LauncherDefault, "brand" to FontFamily.LauncherDefault(),
"plain" to FontFamily.LauncherDefault, "plain" to FontFamily.LauncherDefault(),
), ),
styles = DefaultTextStyles, styles = DefaultTextStyles,
emphasizedStyles = DefaultEmphasizedTextStyles, emphasizedStyles = DefaultEmphasizedTextStyles,
@@ -91,8 +91,8 @@ class TypographyRepository(
builtIn = true, builtIn = true,
name = "Google Sans (Rounded)", name = "Google Sans (Rounded)",
fonts = mapOf( fonts = mapOf(
"brand" to FontFamily.LauncherDefaultRound, "brand" to FontFamily.LauncherDefault(mapOf("ROND" to 100f)),
"plain" to FontFamily.LauncherDefaultRound, "plain" to FontFamily.LauncherDefault(mapOf("ROND" to 100f)),
), ),
styles = DefaultTextStyles, styles = DefaultTextStyles,
emphasizedStyles = DefaultEmphasizedTextStyles, emphasizedStyles = DefaultEmphasizedTextStyles,