forked from geode-sdk/android-launcher
add zoom control
This commit is contained in:
6 files changed
+192
-12
No files matched your search
@@ -68,6 +68,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
private var displayMode = DisplayMode.DEFAULT
|
||||
private var forceRefreshRate = false
|
||||
private var mAspectRatio = 0.0f
|
||||
private var mScreenZoom = 1.0f
|
||||
private var mScreenZoomFit = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setupUIState()
|
||||
@@ -379,6 +381,15 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
frameLayout.aspectRatio = mAspectRatio
|
||||
}
|
||||
|
||||
if (mScreenZoomFit && mScreenZoom != 1.0f) {
|
||||
frameLayout.scaleX = 1/mScreenZoom
|
||||
frameLayout.scaleY = 1/mScreenZoom
|
||||
}
|
||||
|
||||
if (mScreenZoom != 1.0f) {
|
||||
frameLayout.zoom = mScreenZoom
|
||||
}
|
||||
|
||||
val editTextLayoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
@@ -434,6 +445,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
)
|
||||
|
||||
mAspectRatio = ratioForPreference(preferenceUtils.getString(PreferenceUtils.Key.CUSTOM_ASPECT_RATIO) ?: "16_9")
|
||||
mScreenZoom = preferenceUtils.getInt(PreferenceUtils.Key.SCREEN_ZOOM) / 100.0f
|
||||
mScreenZoomFit = preferenceUtils.getBoolean(PreferenceUtils.Key.SCREEN_ZOOM_FIT)
|
||||
|
||||
forceRefreshRate = preferenceUtils.getBoolean(PreferenceUtils.Key.FORCE_HRR)
|
||||
|
||||
|
||||
@@ -366,6 +366,22 @@ fun SettingsScreen(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
SettingsRangeCard(
|
||||
title = stringResource(R.string.preference_screen_zoom_name),
|
||||
dialogTitle = stringResource(R.string.preference_screen_zoom_select),
|
||||
preferenceKey = PreferenceUtils.Key.SCREEN_ZOOM,
|
||||
labelSuffix = "x",
|
||||
range = 25..100,
|
||||
scale = 100,
|
||||
step = 5,
|
||||
) {
|
||||
SettingsCard(
|
||||
title = context.getString(R.string.preference_screen_zoom_fit_name),
|
||||
preferenceKey = PreferenceUtils.Key.SCREEN_ZOOM_FIT,
|
||||
asCard = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
OptionsGroup(context.getString(R.string.preference_category_updater)) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
@@ -29,6 +30,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -48,6 +50,8 @@ import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||
import com.geode.launcher.ui.theme.Typography
|
||||
import com.geode.launcher.utils.LabelledText
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
fun toggleSetting(context: Context, preferenceKey: PreferenceUtils.Key): Boolean {
|
||||
@@ -260,6 +264,113 @@ fun StringDialog(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RangeDialog(
|
||||
title: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
onSelect: (Int) -> Unit,
|
||||
labelSuffix: String,
|
||||
initialValue: Int,
|
||||
range: IntRange,
|
||||
scale: Int,
|
||||
step: Int,
|
||||
children: @Composable () -> Unit
|
||||
) {
|
||||
var enteredValue by remember {
|
||||
mutableFloatStateOf(initialValue / scale.toFloat())
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { onDismissRequest() },
|
||||
title = {
|
||||
Text(title)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.weight(1.0f, false)
|
||||
) {
|
||||
val precision = log10(scale.toFloat()).roundToInt()
|
||||
|
||||
Slider(
|
||||
value = enteredValue,
|
||||
onValueChange = { enteredValue = it },
|
||||
valueRange = (range.first.toFloat()/scale)..(range.last.toFloat()/scale),
|
||||
steps = ((range.last - range.first) - 1) / step,
|
||||
modifier = Modifier.weight(1.0f)
|
||||
)
|
||||
|
||||
Text("%.${precision}f$labelSuffix".format(enteredValue))
|
||||
}
|
||||
|
||||
children()
|
||||
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { onSelect((enteredValue * scale).roundToInt()) }) {
|
||||
Text(stringResource(R.string.message_box_accept))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.message_box_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsRangeCard(
|
||||
title: String,
|
||||
dialogTitle: String,
|
||||
preferenceKey: PreferenceUtils.Key,
|
||||
labelSuffix: String,
|
||||
range: IntRange,
|
||||
scale: Int,
|
||||
step: Int,
|
||||
children: @Composable () -> Unit = {}
|
||||
) {
|
||||
var preferenceValue by PreferenceUtils.useIntPreference(preferenceKey)
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
OptionsCard(
|
||||
title = { OptionsTitle(title = title) },
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = {
|
||||
showDialog = true
|
||||
},
|
||||
role = Role.Button
|
||||
)
|
||||
) {
|
||||
val precision = log10(scale.toFloat()).roundToInt()
|
||||
val scaledValue = preferenceValue / scale.toFloat()
|
||||
|
||||
Text("%.${precision}f$labelSuffix".format(scaledValue))
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
RangeDialog(
|
||||
title = dialogTitle,
|
||||
onDismissRequest = { showDialog = false },
|
||||
onSelect = {
|
||||
preferenceValue = it
|
||||
showDialog = false
|
||||
},
|
||||
initialValue = preferenceValue,
|
||||
range = range,
|
||||
scale = scale,
|
||||
labelSuffix = labelSuffix,
|
||||
step = step,
|
||||
children = children,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal val LocalSelectValue = compositionLocalOf<Any> { 0 }
|
||||
internal val LocalSelectSetValue = staticCompositionLocalOf<(Any) -> Unit> { {} }
|
||||
|
||||
@@ -367,7 +478,7 @@ fun OptionsButton(title: String, description: String? = null, icon: (@Composable
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsCard(title: String, description: String? = null, icon: (@Composable () -> Unit)? = null, preferenceKey: PreferenceUtils.Key) {
|
||||
fun SettingsCard(title: String, description: String? = null, icon: (@Composable () -> Unit)? = null, asCard: Boolean = true, preferenceKey: PreferenceUtils.Key) {
|
||||
val context = LocalContext.current
|
||||
val settingEnabled = remember {
|
||||
mutableStateOf(getSetting(context, preferenceKey))
|
||||
@@ -382,13 +493,18 @@ fun SettingsCard(title: String, description: String? = null, icon: (@Composable
|
||||
icon = icon
|
||||
)
|
||||
},
|
||||
modifier = Modifier.toggleable(
|
||||
modifier = if (asCard) Modifier.toggleable(
|
||||
value = settingEnabled.value,
|
||||
onValueChange = { settingEnabled.value = toggleSetting(context, preferenceKey) },
|
||||
role = Role.Switch,
|
||||
)
|
||||
) else Modifier
|
||||
) {
|
||||
Switch(checked = settingEnabled.value, onCheckedChange = null)
|
||||
Switch(
|
||||
checked = settingEnabled.value,
|
||||
onCheckedChange = if (!asCard)
|
||||
{ _ -> settingEnabled.value = toggleSetting(context, preferenceKey) }
|
||||
else null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,26 +2,52 @@ package com.geode.launcher.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.FrameLayout
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ConstrainedFrameLayout(context: Context) : FrameLayout(context) {
|
||||
// avoid running the aspect ratio fix when we don't need it
|
||||
var aspectRatio: Float? = null;
|
||||
var aspectRatio: Float? = null
|
||||
var zoom: Float? = null
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
if (aspectRatio != null) {
|
||||
var paddingX = 0
|
||||
var paddingY = 0
|
||||
|
||||
val currentAspectRatio = aspectRatio
|
||||
if (currentAspectRatio != null) {
|
||||
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val height = MeasureSpec.getSize(heightMeasureSpec)
|
||||
if (width == 0 || height == 0) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
return
|
||||
}
|
||||
|
||||
val calculatedWidth = height * (aspectRatio ?: 1.0f);
|
||||
val calculatedWidth = height * currentAspectRatio
|
||||
val padding = ((width - calculatedWidth) / 2).toInt()
|
||||
|
||||
// ignore paddings where the screen ends up looking odd
|
||||
if (padding > 0) {
|
||||
setPadding(padding, 0, padding, 0)
|
||||
paddingX += padding
|
||||
}
|
||||
|
||||
val currentZoom = zoom
|
||||
if (currentZoom != null) {
|
||||
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val height = MeasureSpec.getSize(heightMeasureSpec)
|
||||
if (width == 0 || height == 0) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
return
|
||||
}
|
||||
|
||||
val zoomFactor = 1 - currentZoom
|
||||
val widthFactor = width/2.0f + paddingX/4.0f
|
||||
val heightFactor = height/2.0f
|
||||
|
||||
paddingX = (widthFactor * zoomFactor).roundToInt()
|
||||
paddingY = (heightFactor * zoomFactor).roundToInt()
|
||||
}
|
||||
|
||||
if (paddingX > 0 || paddingY > 0) {
|
||||
setPadding(paddingX, paddingY, paddingX, paddingY)
|
||||
}
|
||||
|
||||
super.onMeasure(
|
||||
|
||||
@@ -144,6 +144,8 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
||||
CLEANUP_APKS,
|
||||
CUSTOM_SYMBOL_LIST,
|
||||
CUSTOM_ASPECT_RATIO,
|
||||
SCREEN_ZOOM,
|
||||
SCREEN_ZOOM_FIT,
|
||||
}
|
||||
|
||||
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
||||
@@ -156,6 +158,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
||||
|
||||
private fun defaultValueForIntKey(key: Key) = when (key) {
|
||||
Key.DISPLAY_MODE -> if (this.getBoolean(Key.LIMIT_ASPECT_RATIO)) 1 else 0
|
||||
Key.SCREEN_ZOOM -> 10
|
||||
// people wanted a reset on nightly anyways, so this is a good excuse to do so
|
||||
else -> 0
|
||||
}
|
||||
@@ -187,6 +190,8 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
||||
Key.CLEANUP_APKS -> "PreferenceCleanupPackages"
|
||||
Key.CUSTOM_SYMBOL_LIST -> "PreferenceCustomSymbolList"
|
||||
Key.CUSTOM_ASPECT_RATIO -> "PreferenceCustomAspectRatio"
|
||||
Key.SCREEN_ZOOM -> "PreferenceScreenZoom"
|
||||
Key.SCREEN_ZOOM_FIT -> "PreferenceScreenZoomFit"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,9 +134,9 @@
|
||||
<string name="preference_force_hrr">Force maximum refresh rate</string>
|
||||
<string name="preference_enable_redesign">✨ Enable UI refresh</string>
|
||||
|
||||
<string name="preference_display_mode_name">Screen scale</string>
|
||||
<string name="preference_display_mode_select">Select screen scale</string>
|
||||
<string name="preference_display_mode_legacy">Limited (16:9)</string>
|
||||
<string name="preference_display_mode_name">Display mode</string>
|
||||
<string name="preference_display_mode_select">Select display mode</string>
|
||||
<string name="preference_display_mode_legacy">Limited (Custom)</string>
|
||||
<string name="preference_display_mode_fullscreen">Edge-to-edge</string>
|
||||
<string name="preference_display_mode_default">Default</string>
|
||||
|
||||
@@ -147,6 +147,10 @@
|
||||
<string name="preference_custom_aspect_ratio_4x3">4:3</string>
|
||||
<string name="preference_custom_aspect_ratio_1x1">1:1</string>
|
||||
|
||||
<string name="preference_screen_zoom_name">Screen zoom</string>
|
||||
<string name="preference_screen_zoom_select">Edit screen zoom</string>
|
||||
<string name="preference_screen_zoom_fit_name">Fit to screen</string>
|
||||
|
||||
<string name="preference_category_updater">Updates</string>
|
||||
<string name="preference_update_automatically_name">Enable automatic updates</string>
|
||||
<string name="preference_release_channel_name">Use nightly release channel</string>
|
||||
|
||||
Reference in new issue
Block a user