From 24406f6aaeeccf909c1e3a1ca6e8cd260537b3e7 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sat, 31 Jan 2026 23:21:55 +0100 Subject: [PATCH] Add settings for variable font axes in typography scheme --- .../typography/TypographySettingsScreen.kt | 129 +++++++++++++++--- .../ui/theme/typography/Typography.kt | 4 +- .../typography/fontfamily/GoogleSansFlex.kt | 12 +- .../typography/fontfamily/VariableFont.kt | 26 +++- .../themes/typography/FontManager.kt | 31 +++-- .../launcher2/themes/typography/Typography.kt | 15 +- .../themes/typography/TypographyRepository.kt | 8 +- 7 files changed, 173 insertions(+), 52 deletions(-) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/typography/TypographySettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/typography/TypographySettingsScreen.kt index e94b7a91f..9a0611b94 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/typography/TypographySettingsScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/typography/TypographySettingsScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding @@ -29,6 +30,7 @@ import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.LeadingIconTab import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedIconToggleButton @@ -46,6 +48,7 @@ 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.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource 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.PreferenceCategory 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.typographyOf import kotlinx.serialization.Serializable @@ -745,24 +749,60 @@ private fun FontPreference( val fontManager = FontManager(context) var showDialog by remember { mutableStateOf(false) } + var showFontSettings by remember { mutableStateOf(false) } - 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, - ) + val fontVariantAxes = remember(value) { + if (value is ThemeFontFamily.VariableFontFamily) { + fontManager.getFontSettings(value) + } else { + emptyList() + } + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.background( + 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( expanded = showDialog, @@ -774,7 +814,8 @@ private fun FontPreference( start = 16.dp, top = 16.dp, end = 16.dp, - bottom = 16.dp + WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding(), + bottom = 16.dp + WindowInsets.navigationBars.asPaddingValues() + .calculateBottomPadding(), ), 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 @@ -860,7 +954,6 @@ private fun FontPickerCategory( private fun getFontName(context: Context, fontFamily: ThemeFontFamily?): String { return when (fontFamily) { 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.DeviceBody -> context.getString(R.string.font_name_device_body) is ThemeFontFamily.System -> fontFamily.name diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/Typography.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/Typography.kt index 1e7c6dbf2..6d8bd53a8 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/Typography.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/Typography.kt @@ -13,7 +13,6 @@ import androidx.compose.ui.unit.sp import de.mm20.launcher2.themes.typography.DefaultEmphasizedTextStyles import de.mm20.launcher2.themes.typography.DefaultTextStyles 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.getDeviceHeadlineFontFamily import de.mm20.launcher2.themes.typography.FontFamily as ThemeFontFamily @@ -317,8 +316,7 @@ fun fontFamilyOf( fontFamily: ThemeFontFamily? ): FontFamily { return when (fontFamily) { - is ThemeFontFamily.LauncherDefault -> GoogleSansFlex - is ThemeFontFamily.LauncherDefaultRound -> GoogleSansFlexRound + is ThemeFontFamily.LauncherDefault -> GoogleSansFlex(fontFamily.settings) is ThemeFontFamily.DeviceHeadline -> getDeviceHeadlineFontFamily(context) is ThemeFontFamily.DeviceBody -> getDeviceBodyFontFamily(context) is ThemeFontFamily.SansSerif -> FontFamily.SansSerif diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/fontfamily/GoogleSansFlex.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/fontfamily/GoogleSansFlex.kt index cfd705b7d..1be0f2f18 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/fontfamily/GoogleSansFlex.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/fontfamily/GoogleSansFlex.kt @@ -1,16 +1,8 @@ package de.mm20.launcher2.ui.theme.typography.fontfamily -import androidx.compose.ui.text.font.FontVariation import de.mm20.launcher2.ui.R - - -val GoogleSansFlex = createVariableFontFamily( +fun GoogleSansFlex(settings: Map) = createVariableFontFamily( R.font.google_sans_flex, - FontVariation.roundness(0f) + settings ) - -val GoogleSansFlexRound = createVariableFontFamily( - R.font.google_sans_flex, - FontVariation.roundness( 100f) -) \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/fontfamily/VariableFont.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/fontfamily/VariableFont.kt index df6c2bda4..aef35a896 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/fontfamily/VariableFont.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/typography/fontfamily/VariableFont.kt @@ -1,17 +1,35 @@ package de.mm20.launcher2.ui.theme.typography.fontfamily import androidx.annotation.FontRes +import androidx.collection.LruCache import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontVariation 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(20) + +internal fun createVariableFontFamily(@FontRes resId: Int, settings: Map): 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() for (weight in 100..900 step 100) { fonts += Font( @@ -38,4 +56,4 @@ internal fun createVariableFontFamily(@FontRes resId: Int, vararg settings: Font ) } return FontFamily(fonts) -} \ No newline at end of file +} diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/typography/FontManager.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/typography/FontManager.kt index 70a7b28b7..974c85174 100644 --- a/data/themes/src/main/java/de/mm20/launcher2/themes/typography/FontManager.kt +++ b/data/themes/src/main/java/de/mm20/launcher2/themes/typography/FontManager.kt @@ -1,11 +1,6 @@ package de.mm20.launcher2.themes.typography 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( val builtIn: List, @@ -14,6 +9,14 @@ data class FontList( val system: List, ) +data class VariableFontAxis( + val name: String, + val label: String, + val range: ClosedFloatingPointRange, + val step: Float, + val defaultValue: Float, +) + class FontManager( private val context: Context ) { @@ -24,12 +27,12 @@ class FontManager( .getIdentifier("config_bodyFontFamily", "string", "android") val deviceHeadlineExists = deviceHeadlineResId != 0 && - context.resources.getString(deviceHeadlineResId).isNotBlank() + context.resources.getString(deviceHeadlineResId).isNotBlank() val deviceBodyExists = deviceBodyResId != 0 && - context.resources.getString(deviceBodyResId).isNotBlank() + context.resources.getString(deviceBodyResId).isNotBlank() return FontList( - builtIn = listOf(FontFamily.LauncherDefault, FontFamily.LauncherDefaultRound), + builtIn = listOf(FontFamily.LauncherDefault()), generic = listOf( FontFamily.SansSerif, FontFamily.Serif, @@ -42,4 +45,16 @@ class FontManager( system = emptyList(), ) } + + fun getFontSettings(font: FontFamily.VariableFontFamily): List { + 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() + } } \ No newline at end of file diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/typography/Typography.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/typography/Typography.kt index 7593e9f9c..d12922008 100644 --- a/data/themes/src/main/java/de/mm20/launcher2/themes/typography/Typography.kt +++ b/data/themes/src/main/java/de/mm20/launcher2/themes/typography/Typography.kt @@ -181,13 +181,18 @@ data class TextStyle( @Serializable sealed interface FontFamily { - @Serializable - @SerialName("launcher_default") - data object LauncherDefault : FontFamily + + sealed interface VariableFontFamily { + val settings: Map + get() = emptyMap() + } @Serializable - @SerialName("launcher_default_round") - data object LauncherDefaultRound : FontFamily + @SerialName("launcher_default") + data class LauncherDefault( + override val settings: Map = emptyMap() + ) : FontFamily, VariableFontFamily + @Serializable @SerialName("device_headline") diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/typography/TypographyRepository.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/typography/TypographyRepository.kt index c61a0b2a4..3d8b4bfb3 100644 --- a/data/themes/src/main/java/de/mm20/launcher2/themes/typography/TypographyRepository.kt +++ b/data/themes/src/main/java/de/mm20/launcher2/themes/typography/TypographyRepository.kt @@ -78,8 +78,8 @@ class TypographyRepository( builtIn = true, name = "Google Sans", fonts = mapOf( - "brand" to FontFamily.LauncherDefault, - "plain" to FontFamily.LauncherDefault, + "brand" to FontFamily.LauncherDefault(), + "plain" to FontFamily.LauncherDefault(), ), styles = DefaultTextStyles, emphasizedStyles = DefaultEmphasizedTextStyles, @@ -91,8 +91,8 @@ class TypographyRepository( builtIn = true, name = "Google Sans (Rounded)", fonts = mapOf( - "brand" to FontFamily.LauncherDefaultRound, - "plain" to FontFamily.LauncherDefaultRound, + "brand" to FontFamily.LauncherDefault(mapOf("ROND" to 100f)), + "plain" to FontFamily.LauncherDefault(mapOf("ROND" to 100f)), ), styles = DefaultTextStyles, emphasizedStyles = DefaultEmphasizedTextStyles,