DEADSOFTWARE

Support screen scale option
authorfredboy <fredboy@protonmail.com>
Wed, 2 Jul 2025 05:24:03 +0000 (12:24 +0700)
committerfredboy <fredboy@protonmail.com>
Wed, 2 Jul 2025 05:24:03 +0000 (12:24 +0700)
16 files changed:
assets/json/menu.json
core/common/src/main/kotlin/ru/fredboy/cavedroid/common/api/ApplicationController.kt
core/data/configuration/src/main/kotlin/ru/fredboy/cavedroid/data/configuration/model/ApplicationContext.kt
core/data/configuration/src/main/kotlin/ru/fredboy/cavedroid/data/configuration/repository/ApplicationContextRepositoryImpl.kt
core/data/configuration/src/main/kotlin/ru/fredboy/cavedroid/data/configuration/store/ApplicationContextStore.kt
core/data/menu/src/main/kotlin/ru/fredboy/cavedroid/data/menu/mapper/MenuButtonMapper.kt
core/domain/configuration/src/main/kotlin/ru/fredboy/cavedroid/domain/configuration/repository/ApplicationContextRepository.kt
core/domain/menu/src/main/kotlin/ru/fredboy/cavedroid/domain/menu/model/MenuButton.kt
core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/CaveDroidApplication.kt
core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/base/BaseScreen.kt
core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/di/MenuComponent.kt
core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/input/MenuInputProcessor.kt
core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/option/annotation/BindsMenuNumericalOption.kt [new file with mode: 0644]
core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/option/numerical/IMenuNumericalOption.kt [new file with mode: 0644]
core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/option/numerical/ScreenScaleMenuNumericalOption.kt [new file with mode: 0644]
core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/renderer/MenuRenderer.kt

index f85dec74db11e43e32a49e8c6c8703e3436bd2b6..409fd8229535c240883abea9d232661aac2a834e 100644 (file)
     }
   },
   "options": {
+    "screen_scale": {
+      "type": "numerical_option",
+      "label": "Screen Scale: x%d",
+      "actionKey": "screen_scale_action",
+      "options": ["screen_scale"]
+    },
     "dyncam": {
       "type": "boolean_option",
       "label": "Dynamic Camera: %s",
index 52e73374f3add69fac806a5146acc5daf402e874..440f47134b0e1be3b3cebc368d1a8b367dc04181 100644 (file)
@@ -11,4 +11,6 @@ interface ApplicationController {
     fun loadGame()
 
     fun exitGame()
+
+    fun triggerResize()
 }
index 9baf05a82bd364c85e7bb58d2cfb03b2b5f7447f..b103291fbbc9508939670c74899dcccb6a033400 100644 (file)
@@ -8,4 +8,5 @@ class ApplicationContext(
     internal var gameDirectory: String,
     internal var width: Float,
     internal var height: Float,
+    internal var screenScale: Int,
 )
index 83f87d38775a70bc2f7990e9020fe9a442f4eeec..d7557dc593cb105d4becbebc073e37d79aea32df 100644 (file)
@@ -25,6 +25,8 @@ class ApplicationContextRepositoryImpl @Inject constructor(
 
     override fun getHeight(): Float = applicationContextStore.height
 
+    override fun getScreenScale(): Int = applicationContextStore.screenScale
+
     override fun setTouch(isTouch: Boolean) {
         applicationContextStore.isTouch = isTouch
     }
@@ -59,4 +61,8 @@ class ApplicationContextRepositoryImpl @Inject constructor(
     override fun setHeight(height: Float) {
         applicationContextStore.height = height
     }
+
+    override fun setScreenScale(scale: Int) {
+        applicationContextStore.screenScale = scale
+    }
 }
index 218dc6c766fcdb0603f3dd345f0fe1227ab7dabf..7670bb86e8922dc660deba20352bd75185a40543 100644 (file)
@@ -46,8 +46,16 @@ class ApplicationContextStore @Inject constructor(
         get() = synchronized(lock) { applicationContext.height }
         set(value) = synchronized(lock) { applicationContext.height = value }
 
+    var screenScale: Int
+        get() = synchronized(lock) { applicationContext.screenScale }
+        set(value) = synchronized(lock) {
+            applicationContext.screenScale = value
+            preferencesStore.setPreference(KEY_SCREEN_SCALE_PREF, value.toString())
+        }
+
     private companion object {
         private const val KEY_FULLSCREEN_PREF = "fullscreen"
         private const val KEY_DYNAMIC_CAMERA_PREF = "dyncam"
+        private const val KEY_SCREEN_SCALE_PREF = "screen_scale"
     }
 }
index 746ca5db1339d70b946638dc223e48b1a56c2e5f..94ad55c783638ed7756734de08db009d6853c4a1 100644 (file)
@@ -20,6 +20,14 @@ class MenuButtonMapper @Inject constructor() {
             isEnabled = dto.enabled ?: true,
         )
 
+        "numerical_option" -> MenuButton.NumericalOption(
+            label = dto.label.toString(),
+            isVisible = mapVisibility(dto.visibility),
+            actionKey = dto.actionKey.orEmpty(),
+            optionKeys = dto.options.orEmpty(),
+            isEnabled = dto.enabled ?: true,
+        )
+
         "default", null -> MenuButton.Simple(
             label = dto.label.toString(),
             isVisible = mapVisibility(dto.visibility),
@@ -34,6 +42,7 @@ class MenuButtonMapper @Inject constructor() {
     fun setButtonEnabled(button: MenuButton, isEnabled: Boolean): MenuButton = when (button) {
         is MenuButton.Simple -> button.copy(isEnabled = isEnabled)
         is MenuButton.BooleanOption -> button.copy(isEnabled = isEnabled)
+        is MenuButton.NumericalOption -> button.copy(isEnabled = isEnabled)
     }
 
     private fun mapVisibility(dto: MenuButtonVisibilityDto?): Boolean {
index 47bd2b43a1145c86f4a68fc8187f7328a1a10a16..fd0070e79923a98a45028eaa4eea96e2abd2f813 100644 (file)
@@ -16,6 +16,8 @@ interface ApplicationContextRepository {
 
     fun getHeight(): Float
 
+    fun getScreenScale(): Int
+
     fun setTouch(isTouch: Boolean)
 
     fun setFullscreen(fullscreen: Boolean)
@@ -27,4 +29,6 @@ interface ApplicationContextRepository {
     fun setWidth(width: Float)
 
     fun setHeight(height: Float)
+
+    fun setScreenScale(scale: Int)
 }
index 95a4abb5eb589d200b189f6b5ce4132bd1fd4e0f..d467d388902f5230fd5c569f41344977dfb798e1 100644 (file)
@@ -14,11 +14,23 @@ sealed class MenuButton {
         override val isEnabled: Boolean,
     ) : MenuButton()
 
+    sealed class Option : MenuButton() {
+        abstract val optionKeys: List<String>
+    }
+
     data class BooleanOption(
         override val label: String,
         override val isVisible: Boolean,
         override val actionKey: String,
         override val isEnabled: Boolean,
-        val optionKeys: List<String>,
-    ) : MenuButton()
+        override val optionKeys: List<String>,
+    ) : Option()
+
+    data class NumericalOption(
+        override val label: String,
+        override val isVisible: Boolean,
+        override val actionKey: String,
+        override val isEnabled: Boolean,
+        override val optionKeys: List<String>,
+    ) : Option()
 }
index f13e50b17888aec41ac1559705c8ddb2ed5fd8c0..947cf2dc5311dda979835e539faec60edca517d4 100644 (file)
@@ -55,6 +55,7 @@ class CaveDroidApplication(
                     height = height,
                     isFullscreen = isFullscreen,
                     useDynamicCamera = preferencesStore.getPreference("dyncam").toBoolean(),
+                    screenScale = (preferencesStore.getPreference("screen_scale") ?: "1").toInt(),
                 ),
             )
             .applicationController(this)
@@ -98,6 +99,10 @@ class CaveDroidApplication(
         Gdx.app.exit()
     }
 
+    override fun triggerResize() {
+        resize(Gdx.graphics.width, Gdx.graphics.height)
+    }
+
     companion object {
         private const val TAG = "CaveDroidApplication"
         private const val DEFAULT_VIEWPORT_WIDTH = 480f
index d1c21a3f8028b86b2d58112fb00bc8ca00e27ec5..3a215b8dbe319ba74b2d94397d97f01d8a748a49 100644 (file)
@@ -8,11 +8,7 @@ abstract class BaseScreen(
 ) : Screen {
 
     override fun resize(width: Int, height: Int) {
-        applicationContextRepository.setWidth(width.toFloat() * SCALE)
-        applicationContextRepository.setHeight(height.toFloat() * SCALE)
-    }
-
-    companion object {
-        private const val SCALE = .5f
+        applicationContextRepository.setWidth(width.toFloat() / applicationContextRepository.getScreenScale())
+        applicationContextRepository.setHeight(height.toFloat() / applicationContextRepository.getScreenScale())
     }
 }
index 22ae838fb9242dcbbb04a1d81666fb050e69ddcd..e5560102182be3db238f7dded7c45d8a6de22808 100644 (file)
@@ -3,6 +3,7 @@ package ru.fredboy.cavedroid.zygote.menu.di
 import dagger.Component
 import ru.deadsoftware.cavedroid.generated.module.MenuActionsModule
 import ru.deadsoftware.cavedroid.generated.module.MenuBooleanOptionsModule
+import ru.deadsoftware.cavedroid.generated.module.MenuNumericalOptionsModule
 import ru.fredboy.cavedroid.common.di.MenuScope
 import ru.fredboy.cavedroid.data.menu.di.DataMenuModule
 import ru.fredboy.cavedroid.domain.menu.repository.MenuButtonRepository
@@ -17,6 +18,7 @@ import ru.fredboy.cavedroid.zygote.menu.renderer.MenuRenderer
         DataMenuModule::class,
         MenuBooleanOptionsModule::class,
         MenuActionsModule::class,
+        MenuNumericalOptionsModule::class,
     ],
 )
 interface MenuComponent {
index b01dbe4d6756a25869b782abdc51f0691e265f69..1e306665cc2d4745c5cd958db65c8a91c88bd236 100644 (file)
@@ -9,6 +9,7 @@ import ru.fredboy.cavedroid.domain.menu.model.MenuButton
 import ru.fredboy.cavedroid.domain.menu.repository.MenuButtonRepository
 import ru.fredboy.cavedroid.zygote.menu.action.IMenuAction
 import ru.fredboy.cavedroid.zygote.menu.option.bool.IMenuBooleanOption
+import ru.fredboy.cavedroid.zygote.menu.option.numerical.IMenuNumericalOption
 import javax.inject.Inject
 
 @MenuScope
@@ -17,6 +18,7 @@ class MenuInputProcessor @Inject constructor(
     private val menuButtonRepository: MenuButtonRepository,
     private val menuButtonActions: Map<String, @JvmSuppressWildcards IMenuAction>,
     private val menuButtonBooleanOption: Map<String, @JvmSuppressWildcards IMenuBooleanOption>,
+    private val buttonNumericalOptions: Map<String, @JvmSuppressWildcards IMenuNumericalOption>,
 ) : InputProcessor {
 
     override fun touchUp(
@@ -63,6 +65,14 @@ class MenuInputProcessor @Inject constructor(
                             }
                         }
                     }
+
+                    is MenuButton.NumericalOption -> {
+                        menuButton.optionKeys.forEach { optionKey ->
+                            buttonNumericalOptions[optionKey]?.setNextOption() ?: run {
+                                Gdx.app.error(TAG, "Menu option handler for option '$optionKey' not found")
+                            }
+                        }
+                    }
                 }
             }
         }
diff --git a/core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/option/annotation/BindsMenuNumericalOption.kt b/core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/option/annotation/BindsMenuNumericalOption.kt
new file mode 100644 (file)
index 0000000..bd8db8f
--- /dev/null
@@ -0,0 +1,12 @@
+package ru.fredboy.cavedroid.zygote.menu.option.annotation
+
+import ru.fredboy.automultibind.annotations.BindsIntoMapStringKey
+import ru.fredboy.cavedroid.common.automultibind.MultibindingConfig
+import ru.fredboy.cavedroid.zygote.menu.option.numerical.IMenuNumericalOption
+
+@BindsIntoMapStringKey(
+    interfaceClass = IMenuNumericalOption::class,
+    modulePackage = MultibindingConfig.GENERATED_MODULES_PACKAGE,
+    moduleName = "MenuNumericalOptionsModule",
+)
+annotation class BindsMenuNumericalOption(val stringKey: String)
diff --git a/core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/option/numerical/IMenuNumericalOption.kt b/core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/option/numerical/IMenuNumericalOption.kt
new file mode 100644 (file)
index 0000000..ae08ac5
--- /dev/null
@@ -0,0 +1,8 @@
+package ru.fredboy.cavedroid.zygote.menu.option.numerical
+
+interface IMenuNumericalOption {
+
+    fun getOption(): Number
+
+    fun setNextOption()
+}
diff --git a/core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/option/numerical/ScreenScaleMenuNumericalOption.kt b/core/zygote/src/main/kotlin/ru/fredboy/cavedroid/zygote/menu/option/numerical/ScreenScaleMenuNumericalOption.kt
new file mode 100644 (file)
index 0000000..74ffb61
--- /dev/null
@@ -0,0 +1,30 @@
+package ru.fredboy.cavedroid.zygote.menu.option.numerical
+
+import ru.fredboy.cavedroid.common.api.ApplicationController
+import ru.fredboy.cavedroid.common.di.MenuScope
+import ru.fredboy.cavedroid.domain.configuration.repository.ApplicationContextRepository
+import ru.fredboy.cavedroid.zygote.menu.option.annotation.BindsMenuNumericalOption
+import javax.inject.Inject
+
+@MenuScope
+@BindsMenuNumericalOption(ScreenScaleMenuNumericalOption.KEY)
+class ScreenScaleMenuNumericalOption @Inject constructor(
+    private val applicationContextRepository: ApplicationContextRepository,
+    private val applicationController: ApplicationController,
+) : IMenuNumericalOption {
+
+    override fun getOption(): Number {
+        return applicationContextRepository.getScreenScale()
+    }
+
+    override fun setNextOption() {
+        val nextIndex = (SCALE_VALUES.indexOf(getOption()) + 1) % SCALE_VALUES.size
+        val nextValue = SCALE_VALUES[nextIndex]
+        applicationContextRepository.setScreenScale(nextValue)
+        applicationController.triggerResize()
+    }
+    companion object {
+        const val KEY = "screen_scale"
+        private val SCALE_VALUES = arrayOf(1, 2, 3, 4)
+    }
+}
index 483a7c17456c45f72fcb95cf9155232aa42451df..2433c9d5d42844ccea3ea1a1fc2859c15a6877c3 100644 (file)
@@ -16,6 +16,7 @@ import ru.fredboy.cavedroid.domain.menu.model.MenuButton
 import ru.fredboy.cavedroid.domain.menu.repository.MenuButtonRepository
 import ru.fredboy.cavedroid.zygote.menu.action.IMenuAction
 import ru.fredboy.cavedroid.zygote.menu.option.bool.IMenuBooleanOption
+import ru.fredboy.cavedroid.zygote.menu.option.numerical.IMenuNumericalOption
 import javax.inject.Inject
 
 @MenuScope
@@ -25,6 +26,7 @@ class MenuRenderer @Inject constructor(
     private val getTextureRegionByName: GetTextureRegionByNameUseCase,
     private val menuButtonActions: Map<String, @JvmSuppressWildcards IMenuAction>,
     private val buttonBooleanOptions: Map<String, @JvmSuppressWildcards IMenuBooleanOption>,
+    private val buttonNumericalOptions: Map<String, @JvmSuppressWildcards IMenuNumericalOption>,
     private val getFont: GetFontUseCase,
     private val getStringWidth: GetStringWidthUseCase,
     private val getStringHeight: GetStringHeightUseCase,
@@ -62,7 +64,11 @@ class MenuRenderer @Inject constructor(
             is MenuButton.Simple -> button.label
             is MenuButton.BooleanOption -> String.format(
                 button.label,
-                button.optionKeys.map { key -> buttonBooleanOptions[key]?.getOption().toString() },
+                *button.optionKeys.map { key -> buttonBooleanOptions[key]?.getOption().toString() }.toTypedArray(),
+            )
+            is MenuButton.NumericalOption -> String.format(
+                button.label,
+                *button.optionKeys.mapNotNull { key -> buttonNumericalOptions[key]?.getOption() }.toTypedArray(),
             )
         }