mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-22 08:42:38 +00:00
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 displayMode = DisplayMode.DEFAULT
|
||||||
private var forceRefreshRate = false
|
private var forceRefreshRate = false
|
||||||
private var mAspectRatio = 0.0f
|
private var mAspectRatio = 0.0f
|
||||||
|
private var mScreenZoom = 1.0f
|
||||||
|
private var mScreenZoomFit = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setupUIState()
|
setupUIState()
|
||||||
@@ -379,6 +381,15 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
frameLayout.aspectRatio = mAspectRatio
|
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(
|
val editTextLayoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
@@ -434,6 +445,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
)
|
)
|
||||||
|
|
||||||
mAspectRatio = ratioForPreference(preferenceUtils.getString(PreferenceUtils.Key.CUSTOM_ASPECT_RATIO) ?: "16_9")
|
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)
|
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)) {
|
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.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@@ -29,6 +30,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
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.ui.theme.Typography
|
||||||
import com.geode.launcher.utils.LabelledText
|
import com.geode.launcher.utils.LabelledText
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
|
import kotlin.math.log10
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
fun toggleSetting(context: Context, preferenceKey: PreferenceUtils.Key): Boolean {
|
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 LocalSelectValue = compositionLocalOf<Any> { 0 }
|
||||||
internal val LocalSelectSetValue = staticCompositionLocalOf<(Any) -> Unit> { {} }
|
internal val LocalSelectSetValue = staticCompositionLocalOf<(Any) -> Unit> { {} }
|
||||||
|
|
||||||
@@ -367,7 +478,7 @@ fun OptionsButton(title: String, description: String? = null, icon: (@Composable
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 context = LocalContext.current
|
||||||
val settingEnabled = remember {
|
val settingEnabled = remember {
|
||||||
mutableStateOf(getSetting(context, preferenceKey))
|
mutableStateOf(getSetting(context, preferenceKey))
|
||||||
@@ -382,13 +493,18 @@ fun SettingsCard(title: String, description: String? = null, icon: (@Composable
|
|||||||
icon = icon
|
icon = icon
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier.toggleable(
|
modifier = if (asCard) Modifier.toggleable(
|
||||||
value = settingEnabled.value,
|
value = settingEnabled.value,
|
||||||
onValueChange = { settingEnabled.value = toggleSetting(context, preferenceKey) },
|
onValueChange = { settingEnabled.value = toggleSetting(context, preferenceKey) },
|
||||||
role = Role.Switch,
|
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.content.Context
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ConstrainedFrameLayout(context: Context) : FrameLayout(context) {
|
class ConstrainedFrameLayout(context: Context) : FrameLayout(context) {
|
||||||
// avoid running the aspect ratio fix when we don't need it
|
// 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) {
|
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 width = MeasureSpec.getSize(widthMeasureSpec)
|
||||||
val height = MeasureSpec.getSize(heightMeasureSpec)
|
val height = MeasureSpec.getSize(heightMeasureSpec)
|
||||||
if (width == 0 || height == 0) {
|
if (width == 0 || height == 0) {
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val calculatedWidth = height * (aspectRatio ?: 1.0f);
|
val calculatedWidth = height * currentAspectRatio
|
||||||
val padding = ((width - calculatedWidth) / 2).toInt()
|
val padding = ((width - calculatedWidth) / 2).toInt()
|
||||||
|
|
||||||
// ignore paddings where the screen ends up looking odd
|
// ignore paddings where the screen ends up looking odd
|
||||||
if (padding > 0) {
|
paddingX += padding
|
||||||
setPadding(padding, 0, padding, 0)
|
}
|
||||||
|
|
||||||
|
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(
|
super.onMeasure(
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
|||||||
CLEANUP_APKS,
|
CLEANUP_APKS,
|
||||||
CUSTOM_SYMBOL_LIST,
|
CUSTOM_SYMBOL_LIST,
|
||||||
CUSTOM_ASPECT_RATIO,
|
CUSTOM_ASPECT_RATIO,
|
||||||
|
SCREEN_ZOOM,
|
||||||
|
SCREEN_ZOOM_FIT,
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
||||||
@@ -156,6 +158,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
|||||||
|
|
||||||
private fun defaultValueForIntKey(key: Key) = when (key) {
|
private fun defaultValueForIntKey(key: Key) = when (key) {
|
||||||
Key.DISPLAY_MODE -> if (this.getBoolean(Key.LIMIT_ASPECT_RATIO)) 1 else 0
|
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
|
// people wanted a reset on nightly anyways, so this is a good excuse to do so
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
@@ -187,6 +190,8 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
|||||||
Key.CLEANUP_APKS -> "PreferenceCleanupPackages"
|
Key.CLEANUP_APKS -> "PreferenceCleanupPackages"
|
||||||
Key.CUSTOM_SYMBOL_LIST -> "PreferenceCustomSymbolList"
|
Key.CUSTOM_SYMBOL_LIST -> "PreferenceCustomSymbolList"
|
||||||
Key.CUSTOM_ASPECT_RATIO -> "PreferenceCustomAspectRatio"
|
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_force_hrr">Force maximum refresh rate</string>
|
||||||
<string name="preference_enable_redesign">✨ Enable UI refresh</string>
|
<string name="preference_enable_redesign">✨ Enable UI refresh</string>
|
||||||
|
|
||||||
<string name="preference_display_mode_name">Screen scale</string>
|
<string name="preference_display_mode_name">Display mode</string>
|
||||||
<string name="preference_display_mode_select">Select screen scale</string>
|
<string name="preference_display_mode_select">Select display mode</string>
|
||||||
<string name="preference_display_mode_legacy">Limited (16:9)</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_fullscreen">Edge-to-edge</string>
|
||||||
<string name="preference_display_mode_default">Default</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_4x3">4:3</string>
|
||||||
<string name="preference_custom_aspect_ratio_1x1">1:1</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_category_updater">Updates</string>
|
||||||
<string name="preference_update_automatically_name">Enable automatic updates</string>
|
<string name="preference_update_automatically_name">Enable automatic updates</string>
|
||||||
<string name="preference_release_channel_name">Use nightly release channel</string>
|
<string name="preference_release_channel_name">Use nightly release channel</string>
|
||||||
|
|||||||
Reference in new issue
Block a user