mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-04-07 17:41:31 -06:00
Merge c3e85d28f5 into 39363cd435
This commit is contained in:
commit
db512b7c0e
@ -38,7 +38,7 @@ import org.citra.citra_emu.display.SecondaryDisplay
|
||||
import org.citra.citra_emu.features.hotkeys.HotkeyUtility
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.fragments.EmulationFragment
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
@ -52,14 +52,13 @@ import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.RefreshRateUtil
|
||||
import org.citra.citra_emu.utils.ThemeUtil
|
||||
import org.citra.citra_emu.viewmodel.EmulationViewModel
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
|
||||
class EmulationActivity : AppCompatActivity() {
|
||||
private val preferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
var isActivityRecreated = false
|
||||
private val emulationViewModel: EmulationViewModel by viewModels()
|
||||
val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
val emulationViewModel: EmulationViewModel by viewModels()
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||
private lateinit var hotkeyUtility: HotkeyUtility
|
||||
@ -88,14 +87,22 @@ class EmulationActivity : AppCompatActivity() {
|
||||
RefreshRateUtil.enforceRefreshRate(this, sixtyHz = true)
|
||||
|
||||
ThemeUtil.setTheme(this)
|
||||
settingsViewModel.settings.loadSettings()
|
||||
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
secondaryDisplay = SecondaryDisplay(this)
|
||||
|
||||
// load global settings if for some reason they aren't (should be loaded in MainActivity)
|
||||
if (Settings.settings.getAllGlobal().isEmpty()) {
|
||||
SettingsFile.loadSettings(Settings.settings)
|
||||
}
|
||||
// once per-game settings are added, load them here!
|
||||
|
||||
secondaryDisplay = SecondaryDisplay(this, Settings.settings)
|
||||
secondaryDisplay.updateDisplay()
|
||||
|
||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, Settings.settings)
|
||||
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this, Settings.settings)
|
||||
setContentView(binding.root)
|
||||
|
||||
val navHostFragment =
|
||||
@ -142,7 +149,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
enableFullscreenImmersive()
|
||||
applyOrientationSettings() // Check for orientation settings changes on runtime
|
||||
applyOrientationSettings()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
@ -179,6 +186,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||
secondaryDisplay.releasePresentation()
|
||||
secondaryDisplay.releaseVD()
|
||||
|
||||
Settings.settings.removePerGameSettings()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@ -229,11 +238,11 @@ class EmulationActivity : AppCompatActivity() {
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun enableFullscreenImmersive() {
|
||||
fun enableFullscreenImmersive() {
|
||||
val attributes = window.attributes
|
||||
|
||||
attributes.layoutInDisplayCutoutMode =
|
||||
if (BooleanSetting.EXPAND_TO_CUTOUT_AREA.boolean) {
|
||||
if (Settings.settings.get(BooleanSetting.EXPAND_TO_CUTOUT_AREA)) {
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
} else {
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
||||
@ -250,8 +259,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyOrientationSettings() {
|
||||
val orientationOption = IntSetting.ORIENTATION_OPTION.int
|
||||
fun applyOrientationSettings() {
|
||||
val orientationOption = Settings.settings.get(IntSetting.ORIENTATION_OPTION)
|
||||
screenAdjustmentUtil.changeActivityOrientation(orientationOption)
|
||||
}
|
||||
|
||||
|
||||
@ -29,13 +29,13 @@ class ScreenAdjustmentUtil(
|
||||
isEnabled,
|
||||
windowManager.defaultDisplay.rotation
|
||||
)
|
||||
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
|
||||
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
||||
settings.update(BooleanSetting.SWAP_SCREEN, isEnabled)
|
||||
SettingsFile.saveSetting(BooleanSetting.SWAP_SCREEN, settings)
|
||||
}
|
||||
|
||||
fun cycleLayouts() {
|
||||
|
||||
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
|
||||
val landscapeLayoutsToCycle = settings.get(IntListSetting.LAYOUTS_TO_CYCLE)
|
||||
val landscapeValues =
|
||||
if (landscapeLayoutsToCycle.isNotEmpty())
|
||||
landscapeLayoutsToCycle.toIntArray()
|
||||
@ -45,12 +45,12 @@ class ScreenAdjustmentUtil(
|
||||
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
|
||||
|
||||
if (NativeLibrary.isPortraitMode) {
|
||||
val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int
|
||||
val currentLayout = settings.get(IntSetting.PORTRAIT_SCREEN_LAYOUT)
|
||||
val pos = portraitValues.indexOf(currentLayout)
|
||||
val layoutOption = portraitValues[(pos + 1) % portraitValues.size]
|
||||
changePortraitOrientation(layoutOption)
|
||||
} else {
|
||||
val currentLayout = IntSetting.SCREEN_LAYOUT.int
|
||||
val currentLayout = settings.get(IntSetting.SCREEN_LAYOUT)
|
||||
val pos = landscapeValues.indexOf(currentLayout)
|
||||
val layoutOption = landscapeValues[(pos + 1) % landscapeValues.size]
|
||||
changeScreenOrientation(layoutOption)
|
||||
@ -58,30 +58,30 @@ class ScreenAdjustmentUtil(
|
||||
}
|
||||
|
||||
fun changePortraitOrientation(layoutOption: Int) {
|
||||
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
|
||||
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
||||
settings.update(IntSetting.PORTRAIT_SCREEN_LAYOUT, layoutOption)
|
||||
SettingsFile.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, settings)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
}
|
||||
|
||||
fun changeScreenOrientation(layoutOption: Int) {
|
||||
IntSetting.SCREEN_LAYOUT.int = layoutOption
|
||||
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
||||
settings.update(IntSetting.SCREEN_LAYOUT, layoutOption)
|
||||
SettingsFile.saveSetting(IntSetting.SCREEN_LAYOUT, settings)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
}
|
||||
|
||||
fun changeActivityOrientation(orientationOption: Int) {
|
||||
val activity = context as? Activity ?: return
|
||||
IntSetting.ORIENTATION_OPTION.int = orientationOption
|
||||
settings.saveSetting(IntSetting.ORIENTATION_OPTION, SettingsFile.FILE_NAME_CONFIG)
|
||||
settings.update(IntSetting.ORIENTATION_OPTION, orientationOption)
|
||||
SettingsFile.saveSetting(IntSetting.ORIENTATION_OPTION, settings)
|
||||
activity.requestedOrientation = orientationOption
|
||||
}
|
||||
|
||||
fun toggleScreenUpright() {
|
||||
val uprightBoolean = BooleanSetting.UPRIGHT_SCREEN.boolean
|
||||
BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean
|
||||
settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
||||
val uprightBoolean = settings.get(BooleanSetting.UPRIGHT_SCREEN)
|
||||
settings.update(BooleanSetting.UPRIGHT_SCREEN, !uprightBoolean)
|
||||
SettingsFile.saveSetting(BooleanSetting.UPRIGHT_SCREEN, settings)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
|
||||
|
||||
@ -16,8 +16,9 @@ import android.view.SurfaceView
|
||||
import android.view.WindowManager
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||
class SecondaryDisplay(val context: Context, private val settings: Settings) : DisplayManager.DisplayListener {
|
||||
private var pres: SecondaryDisplayPresentation? = null
|
||||
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
private val vd: VirtualDisplay
|
||||
@ -70,8 +71,7 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||
|
||||
// decide if we are going to the external display or the internal one
|
||||
var display = getExternalDisplay(context)
|
||||
if (display == null ||
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
|
||||
if (display == null || settings.get(IntSetting.SECONDARY_DISPLAY_LAYOUT) == SecondaryDisplayLayout.NONE.int) {
|
||||
display = vd.display
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,8 @@ import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class HotkeyUtility(
|
||||
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
|
||||
private val context: Context
|
||||
private val context: Context,
|
||||
private val settings: Settings
|
||||
) {
|
||||
|
||||
private val hotkeyButtons = Hotkey.entries.map { it.button }
|
||||
@ -112,7 +113,7 @@ class HotkeyUtility(
|
||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true, settings)
|
||||
Hotkey.QUICKSAVE.button -> {
|
||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||
Toast.makeText(
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
interface AbstractBooleanSetting : AbstractSetting {
|
||||
var boolean: Boolean
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
interface AbstractFloatSetting : AbstractSetting {
|
||||
var float: Float
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
interface AbstractIntSetting : AbstractSetting {
|
||||
var int: Int
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
interface AbstractListSetting<E> : AbstractSetting {
|
||||
var list: List<E>
|
||||
}
|
||||
@ -1,13 +1,16 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
interface AbstractSetting {
|
||||
val key: String?
|
||||
val section: String?
|
||||
interface AbstractSetting<T> {
|
||||
val key: String
|
||||
val section: String
|
||||
val defaultValue: T
|
||||
val isRuntimeEditable: Boolean
|
||||
val valueAsString: String
|
||||
val defaultValue: Any
|
||||
|
||||
fun valueToString(value: T): String = value.toString()
|
||||
|
||||
fun valueFromString(string: String): T?
|
||||
}
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
interface AbstractShortSetting : AbstractSetting {
|
||||
var short: Short
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
interface AbstractStringSetting : AbstractSetting {
|
||||
var string: String
|
||||
}
|
||||
@ -10,7 +10,7 @@ enum class BooleanSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Boolean
|
||||
) : AbstractBooleanSetting {
|
||||
) : AbstractSetting<Boolean> {
|
||||
EXPAND_TO_CUTOUT_AREA(SettingKeys.expand_to_cutout_area(), Settings.SECTION_LAYOUT, false),
|
||||
SPIRV_SHADER_GEN(SettingKeys.spirv_shader_gen(), Settings.SECTION_RENDERER, true),
|
||||
ASYNC_SHADERS(SettingKeys.async_shader_compilation(), Settings.SECTION_RENDERER, false),
|
||||
@ -58,10 +58,13 @@ enum class BooleanSetting(
|
||||
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
|
||||
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false);
|
||||
|
||||
override var boolean: Boolean = defaultValue
|
||||
|
||||
override val valueAsString: String
|
||||
get() = boolean.toString()
|
||||
override fun valueFromString(string: String): Boolean? {
|
||||
return when (string.trim().lowercase()) {
|
||||
"1", "true" -> true
|
||||
"0", "false" -> false
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() {
|
||||
@ -98,7 +101,5 @@ enum class BooleanSetting(
|
||||
|
||||
fun from(key: String): BooleanSetting? =
|
||||
BooleanSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,18 +9,25 @@ import org.citra.citra_emu.features.settings.SettingKeys
|
||||
enum class FloatSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Float
|
||||
) : AbstractFloatSetting {
|
||||
override val defaultValue: Float,
|
||||
val scale:Int = 1
|
||||
) : AbstractSetting<Float> {
|
||||
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
|
||||
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
|
||||
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);
|
||||
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f, 255),
|
||||
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f, 255),
|
||||
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f, 255),
|
||||
AUDIO_VOLUME(SettingKeys.volume(), Settings.SECTION_AUDIO, 100f, 100);
|
||||
|
||||
override var float: Float = defaultValue
|
||||
// valueFromString reads raw setting from file, scales up for UI
|
||||
override fun valueFromString(string: String): Float? {
|
||||
return string.toFloatOrNull()?.times(scale)
|
||||
}
|
||||
|
||||
override val valueAsString: String
|
||||
get() = float.toString()
|
||||
// valueToString scales back down to raw for file
|
||||
override fun valueToString(value: Float): String {
|
||||
return (value / scale).toString()
|
||||
}
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() {
|
||||
@ -36,7 +43,5 @@ enum class FloatSetting(
|
||||
private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
|
||||
|
||||
fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue }
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,28 +9,17 @@ enum class IntListSetting(
|
||||
override val section: String,
|
||||
override val defaultValue: List<Int>,
|
||||
val canBeEmpty: Boolean = true
|
||||
) : AbstractListSetting<Int> {
|
||||
) : AbstractSetting<List<Int>> {
|
||||
|
||||
LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
|
||||
|
||||
private var backingList: List<Int> = defaultValue
|
||||
private var lastValidList : List<Int> = defaultValue
|
||||
|
||||
override var list: List<Int>
|
||||
get() = backingList
|
||||
set(value) {
|
||||
if (!canBeEmpty && value.isEmpty()) {
|
||||
backingList = lastValidList
|
||||
} else {
|
||||
backingList = value
|
||||
lastValidList = value
|
||||
}
|
||||
}
|
||||
|
||||
override val valueAsString: String
|
||||
get() = list.joinToString()
|
||||
|
||||
override fun valueToString(value: List<Int>): String = value.joinToString()
|
||||
|
||||
override fun valueFromString(string: String): List<Int>? {
|
||||
return string.split(",")
|
||||
.mapNotNull { it.trim().toIntOrNull() }
|
||||
.takeIf { canBeEmpty || it.isNotEmpty() }
|
||||
}
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() {
|
||||
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||
@ -46,7 +35,5 @@ enum class IntListSetting(
|
||||
|
||||
fun from(key: String): IntListSetting? =
|
||||
values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = values().forEach { it.list = it.defaultValue }
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ enum class IntSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Int
|
||||
) : AbstractIntSetting {
|
||||
) : AbstractSetting<Int> {
|
||||
FRAME_LIMIT(SettingKeys.frame_limit(), Settings.SECTION_RENDERER, 100),
|
||||
EMULATED_REGION(SettingKeys.region_value(), Settings.SECTION_SYSTEM, -1),
|
||||
INIT_CLOCK(SettingKeys.init_clock(), Settings.SECTION_SYSTEM, 0),
|
||||
@ -50,7 +50,6 @@ enum class IntSetting(
|
||||
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
|
||||
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
|
||||
TEXTURE_SAMPLING(SettingKeys.texture_sampling(), Settings.SECTION_RENDERER, 0),
|
||||
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, 1),
|
||||
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
|
||||
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
|
||||
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
|
||||
@ -58,10 +57,15 @@ enum class IntSetting(
|
||||
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0),
|
||||
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
|
||||
|
||||
override var int: Int = defaultValue
|
||||
|
||||
override val valueAsString: String
|
||||
get() = int.toString()
|
||||
override fun valueFromString(string: String): Int? {
|
||||
return string.toIntOrNull() ?: when (string.trim().lowercase()) {
|
||||
"true" -> 1
|
||||
"false" -> 0
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() {
|
||||
@ -83,6 +87,5 @@ enum class IntSetting(
|
||||
|
||||
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class ScaledFloatSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Float,
|
||||
val scale: Int
|
||||
) : AbstractFloatSetting {
|
||||
AUDIO_VOLUME(SettingKeys.volume(), Settings.SECTION_AUDIO, 1.0f, 100);
|
||||
|
||||
override var float: Float = defaultValue
|
||||
get() = field * scale
|
||||
set(value) {
|
||||
field = value / scale
|
||||
}
|
||||
|
||||
override val valueAsString: String get() = (float / scale).toString()
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() {
|
||||
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||
if (setting == this) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val NOT_RUNTIME_EDITABLE = emptyList<ScaledFloatSetting>()
|
||||
|
||||
fun from(key: String): ScaledFloatSetting? =
|
||||
ScaledFloatSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = ScaledFloatSetting.values().forEach { it.float = it.defaultValue * it.scale }
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
/**
|
||||
* A semantically-related group of Settings objects. These Settings are
|
||||
* internally stored as a HashMap.
|
||||
*/
|
||||
class SettingSection(val name: String) {
|
||||
val settings = HashMap<String, AbstractSetting>()
|
||||
|
||||
/**
|
||||
* Convenience method; inserts a value directly into the backing HashMap.
|
||||
*
|
||||
* @param setting The Setting to be inserted.
|
||||
*/
|
||||
fun putSetting(setting: AbstractSetting) {
|
||||
settings[setting.key!!] = setting
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method; gets a value directly from the backing HashMap.
|
||||
*
|
||||
* @param key Used to retrieve the Setting.
|
||||
* @return A Setting object (you should probably cast this before using)
|
||||
*/
|
||||
fun getSetting(key: String): AbstractSetting? {
|
||||
return settings[key]
|
||||
}
|
||||
|
||||
fun mergeSection(settingSection: SettingSection) {
|
||||
for (setting in settingSection.settings.values) {
|
||||
putSetting(setting)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,101 +4,94 @@
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import android.text.TextUtils
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsActivityView
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import java.util.TreeMap
|
||||
|
||||
class Settings {
|
||||
private var gameId: String? = null
|
||||
private val globalValues = HashMap<String, Any>()
|
||||
private val perGameOverrides = HashMap<String, Any>()
|
||||
|
||||
var isLoaded = false
|
||||
var gameId: String? = null
|
||||
|
||||
fun isPerGame(): Boolean = gameId != null && gameId != ""
|
||||
|
||||
fun <T> get(setting: AbstractSetting<T>): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return (perGameOverrides[setting.key]
|
||||
?: globalValues[setting.key]
|
||||
?: setting.defaultValue) as T
|
||||
}
|
||||
|
||||
fun <T> getGlobal(setting: AbstractSetting<T>): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return (globalValues[setting.key] ?: setting.defaultValue) as T
|
||||
}
|
||||
|
||||
fun <T> setGlobal(setting: AbstractSetting<T>, value: T) {
|
||||
globalValues[setting.key] = value as Any
|
||||
}
|
||||
|
||||
fun <T> setOverride(setting: AbstractSetting<T>, value: T) {
|
||||
perGameOverrides[setting.key] = value as Any
|
||||
}
|
||||
|
||||
/** Sets the per-game or global setting based on whether this file has ANY per-game setting.
|
||||
* This should be used, for example, by the Settings Activity
|
||||
*/
|
||||
fun <T> set(setting: AbstractSetting<T>, value: T) {
|
||||
if (isPerGame()) setOverride(setting, value) else setGlobal(setting, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
|
||||
* when getting a key not already in the map
|
||||
* Updates an existing setting honoring whether it is *currently* global or local. This will
|
||||
* be used by the Quick Menu
|
||||
*/
|
||||
class SettingsSectionMap : HashMap<String, SettingSection?>() {
|
||||
override operator fun get(key: String): SettingSection? {
|
||||
if (!super.containsKey(key)) {
|
||||
val section = SettingSection(key)
|
||||
super.put(key, section)
|
||||
return section
|
||||
}
|
||||
return super.get(key)
|
||||
fun <T> update(setting: AbstractSetting<T>, value: T) {
|
||||
if (hasOverride(setting)) setOverride(setting, value) else setGlobal(setting, value)
|
||||
}
|
||||
|
||||
/** Merge the globals from other into the current settings. Merge per-game if game id is the same. */
|
||||
fun mergeSettings(other: Settings) {
|
||||
other.globalValues.forEach{ (key, value) ->
|
||||
globalValues[key] = value
|
||||
}
|
||||
|
||||
if (gameId != other.gameId) return
|
||||
|
||||
perGameOverrides.clear()
|
||||
other.perGameOverrides.forEach{ (key, value) ->
|
||||
perGameOverrides[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
|
||||
|
||||
fun getSection(sectionName: String): SettingSection? {
|
||||
return sections[sectionName]
|
||||
fun <T> clearOverride(setting: AbstractSetting<T>) {
|
||||
perGameOverrides.remove(setting.key)
|
||||
}
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = sections.isEmpty()
|
||||
|
||||
fun loadSettings(view: SettingsActivityView? = null) {
|
||||
sections = SettingsSectionMap()
|
||||
loadCitraSettings(view)
|
||||
if (!TextUtils.isEmpty(gameId)) {
|
||||
loadCustomGameSettings(gameId!!, view)
|
||||
}
|
||||
isLoaded = true
|
||||
fun hasOverride(setting: AbstractSetting<*>): Boolean {
|
||||
return perGameOverrides.containsKey(setting.key)
|
||||
}
|
||||
|
||||
private fun loadCitraSettings(view: SettingsActivityView?) {
|
||||
for ((fileName) in configFileSectionsMap) {
|
||||
sections.putAll(SettingsFile.readFile(fileName, view))
|
||||
}
|
||||
fun getAllOverrides(): Map<String, Any> = perGameOverrides.toMap()
|
||||
|
||||
fun getAllGlobal(): Map<String, Any> = globalValues.toMap()
|
||||
|
||||
fun clearAll() {
|
||||
globalValues.clear()
|
||||
perGameOverrides.clear()
|
||||
}
|
||||
|
||||
private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
|
||||
// Custom game settings
|
||||
mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
|
||||
fun clearOverrides() {
|
||||
perGameOverrides.clear()
|
||||
}
|
||||
|
||||
private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
|
||||
for ((key, updatedSection) in updatedSections) {
|
||||
if (sections.containsKey(key)) {
|
||||
val originalSection = sections[key]
|
||||
originalSection!!.mergeSection(updatedSection!!)
|
||||
} else {
|
||||
sections[key] = updatedSection
|
||||
}
|
||||
}
|
||||
fun removePerGameSettings() {
|
||||
clearOverrides()
|
||||
gameId = null
|
||||
}
|
||||
|
||||
fun loadSettings(gameId: String, view: SettingsActivityView) {
|
||||
this.gameId = gameId
|
||||
loadSettings(view)
|
||||
}
|
||||
|
||||
fun saveSettings(view: SettingsActivityView) {
|
||||
if (TextUtils.isEmpty(gameId)) {
|
||||
view.showToastMessage(
|
||||
CitraApplication.appContext.getString(R.string.ini_saved),
|
||||
false
|
||||
)
|
||||
for ((fileName, sectionNames) in configFileSectionsMap.entries) {
|
||||
val iniSections = TreeMap<String, SettingSection?>()
|
||||
for (section in sectionNames) {
|
||||
iniSections[section] = sections[section]
|
||||
}
|
||||
SettingsFile.saveFile(fileName, iniSections, view)
|
||||
}
|
||||
} else {
|
||||
// TODO: Implement per game settings
|
||||
}
|
||||
}
|
||||
|
||||
fun saveSetting(setting: AbstractSetting, filename: String) {
|
||||
SettingsFile.saveFile(filename, setting)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val SECTION_CORE = "Core"
|
||||
const val SECTION_SYSTEM = "System"
|
||||
const val SECTION_CAMERA = "Camera"
|
||||
@ -234,20 +227,7 @@ class Settings {
|
||||
|
||||
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
||||
|
||||
init {
|
||||
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
|
||||
listOf(
|
||||
SECTION_CORE,
|
||||
SECTION_SYSTEM,
|
||||
SECTION_CAMERA,
|
||||
SECTION_CONTROLS,
|
||||
SECTION_RENDERER,
|
||||
SECTION_LAYOUT,
|
||||
SECTION_STORAGE,
|
||||
SECTION_UTILITY,
|
||||
SECTION_AUDIO,
|
||||
SECTION_DEBUG
|
||||
)
|
||||
}
|
||||
/** Stores the settings as a singleton available everywhere.*/
|
||||
val settings = Settings()
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -7,5 +7,7 @@ package org.citra.citra_emu.features.settings.model
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class SettingsViewModel : ViewModel() {
|
||||
// the Settings Activity will always work with a local copy of settings while
|
||||
// editing, to avoid issues with conflicting active/edited settings
|
||||
val settings = Settings()
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ enum class StringSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: String
|
||||
) : AbstractStringSetting {
|
||||
) : AbstractSetting<String>{
|
||||
INIT_TIME(SettingKeys.init_time(), Settings.SECTION_SYSTEM, "946731601"),
|
||||
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
|
||||
@ -19,10 +19,7 @@ enum class StringSetting(
|
||||
CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
|
||||
|
||||
override var string: String = defaultValue
|
||||
|
||||
override val valueAsString: String
|
||||
get() = string
|
||||
override fun valueFromString(string: String) = string
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() {
|
||||
@ -47,6 +44,5 @@ enum class StringSetting(
|
||||
|
||||
fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,33 +1,41 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class DateTimeSetting(
|
||||
setting: AbstractSetting?,
|
||||
private val settings: Settings,
|
||||
setting: AbstractSetting<String>?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val key: String? = null,
|
||||
private val defaultValue: String? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
private val getValue: (()->String)? = null,
|
||||
private val setValue: ((String)-> Unit)? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_DATETIME_SETTING
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val value: String
|
||||
get() = if (setting != null) {
|
||||
val setting = setting as AbstractStringSetting
|
||||
setting.string
|
||||
} else {
|
||||
defaultValue!!
|
||||
}
|
||||
get() = getValue?.invoke()
|
||||
?: if (setting != null) {
|
||||
settings.get(setting as AbstractSetting<String>)
|
||||
} else {
|
||||
defaultValue!!
|
||||
}
|
||||
|
||||
fun setSelectedValue(datetime: String): AbstractStringSetting {
|
||||
val stringSetting = setting as AbstractStringSetting
|
||||
stringSetting.string = datetime
|
||||
return stringSetting
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun setSelectedValue(datetime: String) {
|
||||
if (setValue != null) {
|
||||
setValue(datetime)
|
||||
}else {
|
||||
val stringSetting = setting as AbstractSetting<String>
|
||||
settings.set(stringSetting, datetime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
@ -17,11 +17,10 @@ import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.hotkeys.Hotkey
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class InputBindingSetting(
|
||||
val abstractSetting: AbstractSetting,
|
||||
val abstractSetting: AbstractSetting<*>,
|
||||
titleId: Int
|
||||
) : SettingsItem(abstractSetting, titleId, 0) {
|
||||
private val context: Context get() = CitraApplication.appContext
|
||||
@ -161,11 +160,12 @@ class InputBindingSetting(
|
||||
/**
|
||||
* Removes the old mapping for this key from the settings, e.g. on user clearing the setting.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun removeOldMapping() {
|
||||
// Try remove all possible keys we wrote for this setting
|
||||
val oldKey = preferences.getString(reverseKey, "")
|
||||
if (oldKey != "") {
|
||||
(setting as AbstractStringSetting).string = ""
|
||||
//settings.set(setting as AbstractSetting<String>,"")
|
||||
preferences.edit()
|
||||
.remove(abstractSetting.key) // Used for ui text
|
||||
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
|
||||
|
||||
@ -5,26 +5,34 @@
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class MultiChoiceSetting(
|
||||
setting: AbstractSetting?,
|
||||
val settings: Settings,
|
||||
setting: AbstractSetting<List<Int>>?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val choicesId: Int,
|
||||
val valuesId: Int,
|
||||
val key: String? = null,
|
||||
val defaultValue: List<Int>? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
private val getValue: (()->List<Int>)? = null,
|
||||
private val setValue: ((List<Int>)-> Unit)? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_MULTI_CHOICE
|
||||
|
||||
val selectedValues: List<Int>
|
||||
get() {
|
||||
if (getValue != null) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return getValue.invoke()
|
||||
}
|
||||
if (setting == null) {
|
||||
return defaultValue!!
|
||||
}
|
||||
try {
|
||||
val setting = setting as IntListSetting
|
||||
return setting.list
|
||||
return settings.get(setting as IntListSetting)
|
||||
}catch (_: ClassCastException) {
|
||||
}
|
||||
return defaultValue!!
|
||||
@ -35,12 +43,14 @@ class MultiChoiceSetting(
|
||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||
*
|
||||
* @param selection New value of the int.
|
||||
* @return the existing setting with the new value applied.
|
||||
*/
|
||||
fun setSelectedValue(selection: List<Int>): IntListSetting {
|
||||
val intSetting = setting as IntListSetting
|
||||
intSetting.list = selection
|
||||
return intSetting
|
||||
fun setSelectedValue(selection: List<Int>) {
|
||||
if (setValue != null) {
|
||||
setValue(selection)
|
||||
}else {
|
||||
val intSetting = setting as IntListSetting
|
||||
settings.set(intSetting, selection)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
* file.)
|
||||
*/
|
||||
abstract class SettingsItem(
|
||||
var setting: AbstractSetting?,
|
||||
var setting: AbstractSetting<*>?,
|
||||
val nameId: Int,
|
||||
val descriptionId: Int
|
||||
) {
|
||||
@ -35,6 +35,8 @@ abstract class SettingsItem(
|
||||
return this.isEditable && this.isEnabled
|
||||
}
|
||||
|
||||
|
||||
|
||||
companion object {
|
||||
const val TYPE_HEADER = 0
|
||||
const val TYPE_SWITCH = 1
|
||||
|
||||
@ -1,62 +1,48 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class SingleChoiceSetting(
|
||||
setting: AbstractSetting?,
|
||||
val settings: Settings,
|
||||
setting: AbstractSetting<*>?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val choicesId: Int,
|
||||
val valuesId: Int,
|
||||
val key: String? = null,
|
||||
val defaultValue: Int? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
private val getValue: (()->Int)? = null,
|
||||
private val setValue: ((Int)-> Unit)? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_SINGLE_CHOICE
|
||||
|
||||
val selectedValue: Int
|
||||
get() {
|
||||
if (setting == null) {
|
||||
return defaultValue!!
|
||||
if (getValue != null) {
|
||||
return getValue.invoke()
|
||||
}
|
||||
|
||||
try {
|
||||
val setting = setting as AbstractIntSetting
|
||||
return setting.int
|
||||
} catch (_: ClassCastException) {
|
||||
}
|
||||
|
||||
try {
|
||||
val setting = setting as AbstractShortSetting
|
||||
return setting.short.toInt()
|
||||
} catch (_: ClassCastException) {
|
||||
}
|
||||
|
||||
return defaultValue!!
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val s = (setting as? AbstractSetting<Int>) ?: return defaultValue!!
|
||||
return settings.get(s)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value to the backing int. If that int was previously null,
|
||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||
*
|
||||
* Write a value to the backing int .
|
||||
* @param selection New value of the int.
|
||||
* @return the existing setting with the new value applied.
|
||||
*/
|
||||
fun setSelectedValue(selection: Int): AbstractIntSetting {
|
||||
val intSetting = setting as AbstractIntSetting
|
||||
intSetting.int = selection
|
||||
return intSetting
|
||||
}
|
||||
|
||||
fun setSelectedValue(selection: Short): AbstractShortSetting {
|
||||
val shortSetting = setting as AbstractShortSetting
|
||||
shortSetting.short = selection
|
||||
return shortSetting
|
||||
fun setSelectedValue(selection: Int) {
|
||||
if (setValue != null) {
|
||||
setValue(selection)
|
||||
}else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val backSetting = setting as AbstractSetting<Int>
|
||||
settings.set(backSetting, selection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,15 +4,16 @@
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import org.citra.citra_emu.features.settings.model.AbstractFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class SliderSetting(
|
||||
setting: AbstractSetting?,
|
||||
val settings: Settings,
|
||||
setting: AbstractSetting<*>?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val min: Int,
|
||||
@ -20,17 +21,28 @@ class SliderSetting(
|
||||
val units: String,
|
||||
val key: String? = null,
|
||||
val defaultValue: Float? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
val rounding: Int = 2,
|
||||
override var isEnabled: Boolean = true,
|
||||
private val getValue: (()->Float)? = null,
|
||||
private val setValue: ((Float)-> Unit)? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_SLIDER
|
||||
val selectedFloat: Float
|
||||
get() {
|
||||
val setting = setting ?: return defaultValue!!.toFloat()
|
||||
if (getValue != null) return getValue.invoke()
|
||||
val s = setting ?: return defaultValue!!
|
||||
|
||||
val ret = when (s.defaultValue) {
|
||||
is Int -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
settings.get(s as AbstractSetting<Int>).toFloat()
|
||||
}
|
||||
|
||||
is Float -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
settings.get(s as AbstractSetting<Float>)
|
||||
}
|
||||
|
||||
val ret = when (setting) {
|
||||
is AbstractIntSetting -> setting.int.toFloat()
|
||||
is FloatSetting -> setting.float
|
||||
is ScaledFloatSetting -> setting.float
|
||||
else -> {
|
||||
Log.error("[SliderSetting] Error casting setting type.")
|
||||
-1f
|
||||
@ -38,16 +50,32 @@ class SliderSetting(
|
||||
}
|
||||
return ret.coerceIn(min.toFloat(), max.toFloat())
|
||||
}
|
||||
/**
|
||||
* Write a value to the backing int. If that int was previously null,
|
||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||
*
|
||||
* @param selection New value of the int.
|
||||
* @return the existing setting with the new value applied.
|
||||
*/
|
||||
fun setSelectedValue(selection: Int): AbstractIntSetting {
|
||||
val intSetting = setting as AbstractIntSetting
|
||||
intSetting.int = selection
|
||||
fun roundedFloat(value: Float): Float {
|
||||
val factor = 10f.pow(rounding)
|
||||
return (value * factor).roundToInt() / factor
|
||||
}
|
||||
|
||||
val valueAsString: String
|
||||
get() = setting?.let {
|
||||
when (it.defaultValue) {
|
||||
is Int -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
settings.get(it as AbstractSetting<Int>).toString()
|
||||
}
|
||||
|
||||
is Float -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
roundedFloat(settings.get(it as AbstractSetting<Float>)).toString()
|
||||
}
|
||||
|
||||
else -> ""
|
||||
}
|
||||
} ?: defaultValue?.toString() ?: ""
|
||||
|
||||
fun setSelectedValue(selection: Int): AbstractSetting<Int> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val intSetting = setting as AbstractSetting<Int>
|
||||
settings.set(intSetting, selection)
|
||||
return intSetting
|
||||
}
|
||||
|
||||
@ -56,15 +84,14 @@ class SliderSetting(
|
||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||
*
|
||||
* @param selection New value of the float.
|
||||
* @return the existing setting with the new value applied.
|
||||
*/
|
||||
fun setSelectedValue(selection: Float): AbstractFloatSetting {
|
||||
val floatSetting = setting as AbstractFloatSetting
|
||||
if (floatSetting is ScaledFloatSetting) {
|
||||
floatSetting.float = selection
|
||||
} else {
|
||||
floatSetting.float = selection
|
||||
fun setSelectedValue(selection: Float) {
|
||||
if (setValue != null) {
|
||||
setValue(selection)
|
||||
}else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val floatSetting = setting as AbstractSetting<Float>
|
||||
settings.set(floatSetting, selection)
|
||||
}
|
||||
return floatSetting
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,28 +1,40 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class StringInputSetting(
|
||||
setting: AbstractSetting?,
|
||||
val settings: Settings,
|
||||
setting: AbstractSetting<String>?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val defaultValue: String,
|
||||
val characterLimit: Int = 0,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
private val getValue: (()->String)? = null,
|
||||
private val setValue: ((String)-> Unit)? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_STRING_INPUT
|
||||
|
||||
val selectedValue: String
|
||||
get() = setting?.valueAsString ?: defaultValue
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
get() {
|
||||
if (getValue != null) return getValue.invoke()
|
||||
setting ?: return defaultValue
|
||||
return settings.get(setting as AbstractSetting<String>)
|
||||
}
|
||||
|
||||
fun setSelectedValue(selection: String): AbstractStringSetting {
|
||||
val stringSetting = setting as AbstractStringSetting
|
||||
stringSetting.string = selection
|
||||
return stringSetting
|
||||
fun setSelectedValue(selection: String) {
|
||||
if (setValue != null) {
|
||||
setValue.invoke(selection)
|
||||
}else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val stringSetting = setting as AbstractSetting<String>
|
||||
settings.set(stringSetting, selection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class StringSingleChoiceSetting(
|
||||
setting: AbstractSetting?,
|
||||
val settings: Settings,
|
||||
setting: AbstractSetting<String>?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val choices: Array<String>,
|
||||
val values: Array<String>?,
|
||||
val key: String? = null,
|
||||
private val defaultValue: String? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
private val getValue: (()->String)? = null,
|
||||
private val setValue: ((String)-> Unit)? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_STRING_SINGLE_CHOICE
|
||||
|
||||
@ -31,22 +33,12 @@ class StringSingleChoiceSetting(
|
||||
|
||||
val selectedValue: String
|
||||
get() {
|
||||
if (getValue != null) return getValue.invoke()
|
||||
if (setting == null) {
|
||||
return defaultValue!!
|
||||
}
|
||||
|
||||
try {
|
||||
val setting = setting as AbstractStringSetting
|
||||
return setting.string
|
||||
} catch (_: ClassCastException) {
|
||||
}
|
||||
|
||||
try {
|
||||
val setting = setting as AbstractShortSetting
|
||||
return setting.short.toString()
|
||||
} catch (_: ClassCastException) {
|
||||
}
|
||||
return defaultValue!!
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return settings.get(setting as AbstractSetting<String>)
|
||||
}
|
||||
val selectValueIndex: Int
|
||||
get() {
|
||||
@ -64,17 +56,14 @@ class StringSingleChoiceSetting(
|
||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||
*
|
||||
* @param selection New value of the int.
|
||||
* @return the existing setting with the new value applied.
|
||||
*/
|
||||
fun setSelectedValue(selection: String): AbstractStringSetting {
|
||||
val stringSetting = setting as AbstractStringSetting
|
||||
stringSetting.string = selection
|
||||
return stringSetting
|
||||
}
|
||||
|
||||
fun setSelectedValue(selection: Short): AbstractShortSetting {
|
||||
val shortSetting = setting as AbstractShortSetting
|
||||
shortSetting.short = selection
|
||||
return shortSetting
|
||||
fun setSelectedValue(selection: String) {
|
||||
if (setValue != null) {
|
||||
setValue(selection)
|
||||
}else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val stringSetting = setting as AbstractSetting<String>
|
||||
settings.set(stringSetting, selection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,38 +4,45 @@
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class SwitchSetting(
|
||||
setting: AbstractBooleanSetting,
|
||||
val settings: Settings,
|
||||
setting: AbstractSetting<Boolean>?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val key: String? = null,
|
||||
val defaultValue: Boolean = false,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
private val getValue: (() -> Boolean)? = null,
|
||||
private val setValue: ((Boolean) -> Unit)? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_SWITCH
|
||||
|
||||
val isChecked: Boolean
|
||||
get() {
|
||||
if (getValue != null) return getValue.invoke()
|
||||
if (setting == null) {
|
||||
return defaultValue
|
||||
}
|
||||
val setting = setting as AbstractBooleanSetting
|
||||
return setting.boolean
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val setting = setting as AbstractSetting<Boolean>
|
||||
return settings.get(setting)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value to the backing boolean.
|
||||
*
|
||||
* @param checked Pretty self explanatory.
|
||||
* @return the existing setting with the new value applied.
|
||||
*/
|
||||
fun setChecked(checked: Boolean): AbstractBooleanSetting {
|
||||
val setting = setting as AbstractBooleanSetting
|
||||
setting.boolean = checked
|
||||
return setting
|
||||
fun setChecked(checked: Boolean) {
|
||||
if (setValue != null) {
|
||||
setValue(checked)
|
||||
}else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val setting = setting as AbstractSetting<Boolean>
|
||||
settings.set(setting, checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ package org.citra.citra_emu.features.settings.ui
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
@ -27,13 +26,8 @@ import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.ActivitySettingsBinding
|
||||
import java.io.IOException
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
@ -42,12 +36,13 @@ import org.citra.citra_emu.utils.RefreshRateUtil
|
||||
import org.citra.citra_emu.utils.ThemeUtil
|
||||
|
||||
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
private val presenter = SettingsActivityPresenter(this)
|
||||
|
||||
private lateinit var binding: ActivitySettingsBinding
|
||||
val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
private val presenter by lazy { SettingsActivityPresenter(this, settingsViewModel) }
|
||||
|
||||
// the activity will work with the fresh Settings() object created and stored in the viewmodel
|
||||
override val settings: Settings get() = settingsViewModel.settings
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -63,9 +58,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
val launcher = intent
|
||||
val gameID = launcher.getStringExtra(ARG_GAME_ID)
|
||||
val menuTag = launcher.getStringExtra(ARG_MENU_TAG)
|
||||
presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
|
||||
val gameID = launcher.getStringExtra(ARG_GAME_ID) ?: ""
|
||||
val menuTag = launcher.getStringExtra(ARG_MENU_TAG) ?: ""
|
||||
presenter.onCreate(savedInstanceState, menuTag, gameID)
|
||||
|
||||
// Show "Back" button in the action bar for navigation
|
||||
setSupportActionBar(binding.toolbarSettings)
|
||||
@ -211,13 +206,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
controllerKeys.forEach { editor.remove(it) }
|
||||
editor.apply()
|
||||
|
||||
// Reset the static memory representation of each setting
|
||||
BooleanSetting.clear()
|
||||
FloatSetting.clear()
|
||||
ScaledFloatSetting.clear()
|
||||
IntSetting.clear()
|
||||
StringSetting.clear()
|
||||
|
||||
// Delete settings file because the user may have changed values that do not exist in the UI
|
||||
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
|
||||
if (!settingsFile.delete()) {
|
||||
|
||||
@ -12,6 +12,8 @@ import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
@ -19,8 +21,8 @@ import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.utils.TurboHelper
|
||||
|
||||
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
|
||||
val settings: Settings get() = activityView.settings
|
||||
class SettingsActivityPresenter(private val activityView: SettingsActivityView, private val viewModel: SettingsViewModel) {
|
||||
val settings: Settings get() = viewModel.settings
|
||||
|
||||
private var shouldSave = false
|
||||
private lateinit var menuTag: String
|
||||
@ -29,6 +31,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
|
||||
this.menuTag = menuTag
|
||||
this.gameId = gameId
|
||||
// merge the active settings into the local settings activity instance
|
||||
settings.mergeSettings(Settings.settings)
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
|
||||
}
|
||||
@ -47,13 +52,6 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
}
|
||||
|
||||
private fun loadSettingsUI() {
|
||||
if (!settings.isLoaded) {
|
||||
if (!TextUtils.isEmpty(gameId)) {
|
||||
settings.loadSettings(gameId, activityView)
|
||||
} else {
|
||||
settings.loadSettings(activityView)
|
||||
}
|
||||
}
|
||||
activityView.showSettingsFragment(menuTag, false, gameId)
|
||||
activityView.onSettingsFileLoaded()
|
||||
}
|
||||
@ -72,7 +70,8 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
val nomediaFileExists: Boolean
|
||||
try {
|
||||
dataDirTreeUri = PermissionsHandler.citraDirectory
|
||||
dataDirDocument = DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
|
||||
dataDirDocument =
|
||||
DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
|
||||
nomediaFileDocument = dataDirDocument.findFile(".nomedia")
|
||||
nomediaFileExists = (nomediaFileDocument != null)
|
||||
} catch (e: Exception) {
|
||||
@ -80,7 +79,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
return
|
||||
}
|
||||
|
||||
if (BooleanSetting.ANDROID_HIDE_IMAGES.boolean) {
|
||||
if (settings.get(BooleanSetting.ANDROID_HIDE_IMAGES)) {
|
||||
if (!nomediaFileExists) {
|
||||
Log.info("[SettingsActivity]: Attempting to create .nomedia in user data directory")
|
||||
FileUtil.createFile(dataDirTreeUri.toString(), ".nomedia")
|
||||
@ -94,14 +93,18 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
fun onStop(finishing: Boolean) {
|
||||
if (finishing && shouldSave) {
|
||||
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||
settings.saveSettings(activityView)
|
||||
//added to ensure that layout changes take effect as soon as settings window closes
|
||||
if (settings.isPerGame()) {
|
||||
SettingsFile.saveCustomFile(settings,activityView)
|
||||
}else{
|
||||
SettingsFile.saveGlobalFile(settings,activityView)
|
||||
}
|
||||
// merge the edited settings back into the active settings
|
||||
Settings.settings.mergeSettings(settings)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
updateAndroidImageVisibility()
|
||||
TurboHelper.reloadTurbo(false) // TODO: Can this go somewhere else? -OS
|
||||
TurboHelper.reloadTurbo(false, settings) // TODO: Can this go somewhere else? -OS
|
||||
}
|
||||
NativeLibrary.reloadSettings()
|
||||
}
|
||||
|
||||
fun onSettingChanged() {
|
||||
|
||||
@ -35,15 +35,8 @@ import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||
import org.citra.citra_emu.databinding.ListItemSettingBinding
|
||||
import org.citra.citra_emu.databinding.ListItemSettingSwitchBinding
|
||||
import org.citra.citra_emu.databinding.ListItemSettingsHeaderBinding
|
||||
import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
@ -74,9 +67,9 @@ import java.text.SimpleDateFormat
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class SettingsAdapter(
|
||||
private val fragmentView: SettingsFragmentView,
|
||||
public val context: Context
|
||||
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
|
||||
val fragmentView: SettingsFragmentView,
|
||||
val context: Context
|
||||
) : RecyclerView.Adapter<SettingViewHolder<SettingsItem>?>(), DialogInterface.OnClickListener,
|
||||
DialogInterface.OnMultiChoiceClickListener {
|
||||
private var settings: ArrayList<SettingsItem>? = null
|
||||
private var clickedItem: SettingsItem? = null
|
||||
@ -94,7 +87,7 @@ class SettingsAdapter(
|
||||
clickedPosition = -1
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder<*> {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
SettingsItem.TYPE_HEADER -> {
|
||||
@ -144,7 +137,7 @@ class SettingsAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
|
||||
override fun onBindViewHolder(holder: SettingViewHolder<SettingsItem>, position: Int) {
|
||||
getItem(position)?.let { holder.bind(it) }
|
||||
}
|
||||
|
||||
@ -226,8 +219,7 @@ class SettingsAdapter(
|
||||
}
|
||||
|
||||
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
|
||||
val setting = item.setChecked(checked)
|
||||
fragmentView.putSetting(setting)
|
||||
item.setChecked(checked)
|
||||
fragmentView.onSettingChanged()
|
||||
|
||||
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
|
||||
@ -338,8 +330,7 @@ class SettingsAdapter(
|
||||
fragmentView.onSettingChanged()
|
||||
}
|
||||
notifyItemChanged(clickedPosition)
|
||||
val setting = item.setSelectedValue(rtcString)
|
||||
fragmentView.putSetting(setting)
|
||||
item.setSelectedValue(rtcString)
|
||||
fragmentView.loadSettingsList()
|
||||
clickedItem = null
|
||||
}
|
||||
@ -359,7 +350,7 @@ class SettingsAdapter(
|
||||
val sliderBinding = DialogSliderBinding.inflate(inflater)
|
||||
textInputLayout = sliderBinding.textInput
|
||||
textSliderValue = sliderBinding.textValue
|
||||
if (item.setting is FloatSetting) {
|
||||
if (item.setting?.defaultValue is Float) {
|
||||
textSliderValue?.let {
|
||||
it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
|
||||
it.setText(sliderProgress.toString())
|
||||
@ -393,9 +384,9 @@ class SettingsAdapter(
|
||||
})
|
||||
|
||||
addOnChangeListener { _: Slider, value: Float, _: Boolean ->
|
||||
sliderProgress = (value * 100).roundToInt().toFloat() / 100f
|
||||
sliderProgress = item.roundedFloat(value)
|
||||
var sliderString = sliderProgress.toString()
|
||||
if (item.setting !is FloatSetting) {
|
||||
if (item.setting?.defaultValue !is Float) {
|
||||
sliderString = sliderProgress.roundToInt().toString()
|
||||
if (textSliderValue?.text.toString() != sliderString) {
|
||||
textSliderValue?.setText(sliderString)
|
||||
@ -418,16 +409,12 @@ class SettingsAdapter(
|
||||
.setPositiveButton(android.R.string.ok, this)
|
||||
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
||||
.setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int ->
|
||||
sliderBinding.slider?.value = when (item.setting) {
|
||||
is ScaledFloatSetting -> {
|
||||
val scaledSetting = item.setting as ScaledFloatSetting
|
||||
scaledSetting.defaultValue * scaledSetting.scale
|
||||
}
|
||||
|
||||
is FloatSetting -> (item.setting as FloatSetting).defaultValue
|
||||
else -> item.defaultValue ?: 0f
|
||||
sliderBinding.slider.value = when (item.setting?.defaultValue) {
|
||||
is Float -> item.setting!!.defaultValue as Float
|
||||
is Int -> (item.setting!!.defaultValue as Int).toFloat()
|
||||
else -> 0f
|
||||
}
|
||||
onClick(dialog, which)
|
||||
onClick(dialog, which)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
@ -478,26 +465,9 @@ class SettingsAdapter(
|
||||
is SingleChoiceSetting -> {
|
||||
val scSetting = clickedItem as? SingleChoiceSetting
|
||||
scSetting?.let {
|
||||
val setting = when (it.setting) {
|
||||
is AbstractIntSetting -> {
|
||||
val value = getValueForSingleChoiceSelection(it, which)
|
||||
if (it.selectedValue != value) {
|
||||
fragmentView?.onSettingChanged()
|
||||
}
|
||||
it.setSelectedValue(value)
|
||||
}
|
||||
|
||||
is AbstractShortSetting -> {
|
||||
val value = getValueForSingleChoiceSelection(it, which).toShort()
|
||||
if (it.selectedValue.toShort() != value) {
|
||||
fragmentView?.onSettingChanged()
|
||||
}
|
||||
it.setSelectedValue(value)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
|
||||
}
|
||||
fragmentView?.putSetting(setting)
|
||||
val value = getValueForSingleChoiceSelection(it, which)
|
||||
if (it.selectedValue != value) fragmentView?.onSettingChanged()
|
||||
it.setSelectedValue(value)
|
||||
fragmentView.loadSettingsList()
|
||||
closeDialog()
|
||||
}
|
||||
@ -506,22 +476,9 @@ class SettingsAdapter(
|
||||
is StringSingleChoiceSetting -> {
|
||||
val scSetting = clickedItem as? StringSingleChoiceSetting
|
||||
scSetting?.let {
|
||||
val setting = when (it.setting) {
|
||||
is AbstractStringSetting -> {
|
||||
val value = it.getValueAt(which)
|
||||
if (it.selectedValue != value) fragmentView?.onSettingChanged()
|
||||
it.setSelectedValue(value ?: "")
|
||||
}
|
||||
|
||||
is AbstractShortSetting -> {
|
||||
if (it.selectValueIndex != which) fragmentView?.onSettingChanged()
|
||||
it.setSelectedValue(it.getValueAt(which)?.toShort() ?: 1)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unrecognized type used for StringSingleChoiceSetting!")
|
||||
}
|
||||
|
||||
fragmentView?.putSetting(setting)
|
||||
val value = it.getValueAt(which) ?: ""
|
||||
if (it.selectedValue != value) fragmentView?.onSettingChanged()
|
||||
it.setSelectedValue(value)
|
||||
fragmentView.loadSettingsList()
|
||||
closeDialog()
|
||||
}
|
||||
@ -530,21 +487,12 @@ class SettingsAdapter(
|
||||
is SliderSetting -> {
|
||||
val sliderSetting = clickedItem as? SliderSetting
|
||||
sliderSetting?.let {
|
||||
val sliderval = (it.selectedFloat * 100).roundToInt().toFloat() / 100
|
||||
if (sliderval != sliderProgress) {
|
||||
fragmentView?.onSettingChanged()
|
||||
}
|
||||
when (it.setting) {
|
||||
is AbstractIntSetting -> {
|
||||
val value = sliderProgress.roundToInt()
|
||||
val setting = it.setSelectedValue(value)
|
||||
fragmentView?.putSetting(setting)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val setting = it.setSelectedValue(sliderProgress)
|
||||
fragmentView?.putSetting(setting)
|
||||
}
|
||||
val sliderval = it.roundedFloat(sliderProgress)
|
||||
if (sliderval != it.selectedFloat) fragmentView.onSettingChanged()
|
||||
val s = it.setting
|
||||
when {
|
||||
it.setting?.defaultValue is Int -> it.setSelectedValue(sliderProgress.roundToInt())
|
||||
else -> it.setSelectedValue(sliderProgress)
|
||||
}
|
||||
fragmentView.loadSettingsList()
|
||||
closeDialog()
|
||||
@ -557,8 +505,7 @@ class SettingsAdapter(
|
||||
if (it.selectedValue != textInputValue) {
|
||||
fragmentView?.onSettingChanged()
|
||||
}
|
||||
val setting = it.setSelectedValue(textInputValue ?: "")
|
||||
fragmentView?.putSetting(setting)
|
||||
it.setSelectedValue(textInputValue)
|
||||
fragmentView.loadSettingsList()
|
||||
closeDialog()
|
||||
}
|
||||
@ -575,36 +522,19 @@ class SettingsAdapter(
|
||||
mcsetting?.let {
|
||||
val value = getValueForMultiChoiceSelection(it, which)
|
||||
if (it.selectedValues.contains(value) != isChecked) {
|
||||
val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
|
||||
fragmentView?.putSetting(setting)
|
||||
fragmentView?.onSettingChanged()
|
||||
it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
|
||||
fragmentView.onSettingChanged()
|
||||
}
|
||||
fragmentView.loadSettingsList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
|
||||
fun onLongClick(setting: AbstractSetting<*>, position: Int): Boolean {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
when (setting) {
|
||||
is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
|
||||
is AbstractFloatSetting -> {
|
||||
if (setting is ScaledFloatSetting) {
|
||||
setting.float = setting.defaultValue * setting.scale
|
||||
} else {
|
||||
setting.float = setting.defaultValue as Float
|
||||
}
|
||||
}
|
||||
|
||||
is AbstractIntSetting -> setting.int = setting.defaultValue as Int
|
||||
is AbstractStringSetting -> setting.string = setting.defaultValue as String
|
||||
is AbstractShortSetting -> setting.short = setting.defaultValue as Short
|
||||
}
|
||||
notifyItemChanged(position)
|
||||
fragmentView.onSettingChanged()
|
||||
fragmentView.loadSettingsList()
|
||||
resetSettingToDefault(setting, position)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
@ -612,6 +542,13 @@ class SettingsAdapter(
|
||||
return true
|
||||
}
|
||||
|
||||
fun <T> resetSettingToDefault(setting: AbstractSetting<T>, position: Int) {
|
||||
fragmentView.activityView?.settings?.set(setting,setting.defaultValue)
|
||||
notifyItemChanged(position)
|
||||
fragmentView.onSettingChanged()
|
||||
fragmentView.loadSettingsList()
|
||||
}
|
||||
|
||||
fun onInputBindingLongClick(setting: InputBindingSetting, position: Int): Boolean {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
|
||||
@ -13,16 +13,19 @@ import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import org.citra.citra_emu.databinding.FragmentSettingsBinding
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import kotlin.getValue
|
||||
|
||||
class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||
override var activityView: SettingsActivityView? = null
|
||||
|
||||
private val fragmentPresenter = SettingsFragmentPresenter(this)
|
||||
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
||||
|
||||
private val fragmentPresenter by lazy { SettingsFragmentPresenter(this) }
|
||||
private var settingsAdapter: SettingsAdapter? = null
|
||||
|
||||
private var _binding: FragmentSettingsBinding? = null
|
||||
@ -37,7 +40,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||
super.onCreate(savedInstanceState)
|
||||
val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
|
||||
val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
|
||||
fragmentPresenter.onCreate(menuTag!!, gameId!!)
|
||||
fragmentPresenter.onCreate(menuTag!!, gameId!!, settingsViewModel.settings)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
@ -88,10 +91,6 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||
activityView!!.showToastMessage(message!!, is_long)
|
||||
}
|
||||
|
||||
override fun putSetting(setting: AbstractSetting) {
|
||||
fragmentPresenter.putSetting(setting)
|
||||
}
|
||||
|
||||
override fun onSettingChanged() {
|
||||
activityView!!.onSettingChanged()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,9 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.ui
|
||||
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
|
||||
/**
|
||||
@ -45,13 +44,6 @@ interface SettingsFragmentView {
|
||||
*/
|
||||
fun showToastMessage(message: String?, is_long: Boolean)
|
||||
|
||||
/**
|
||||
* Have the fragment add a setting to the HashMap.
|
||||
*
|
||||
* @param setting The (possibly previously missing) new setting.
|
||||
*/
|
||||
fun putSetting(setting: AbstractSetting)
|
||||
|
||||
/**
|
||||
* Have the fragment tell the containing Activity that a setting was modified.
|
||||
*/
|
||||
|
||||
@ -18,12 +18,11 @@ import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: DateTimeSetting
|
||||
|
||||
SettingViewHolder<DateTimeSetting>(binding.root, adapter) {
|
||||
override lateinit var setting: DateTimeSetting
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as DateTimeSetting
|
||||
setting = item as? DateTimeSetting ?: return
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -10,8 +10,8 @@ import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
||||
SettingViewHolder<SettingsItem>(binding.root, adapter) {
|
||||
override var setting = null
|
||||
init {
|
||||
itemView.setOnClickListener(null)
|
||||
}
|
||||
|
||||
@ -13,8 +13,8 @@ import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class InputBindingSettingViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: InputBindingSetting
|
||||
SettingViewHolder<InputBindingSetting>(binding.root, adapter) {
|
||||
override lateinit var setting: InputBindingSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
|
||||
@ -11,9 +11,8 @@ import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: SettingsItem
|
||||
|
||||
SettingViewHolder<SettingsItem>(binding.root, adapter) {
|
||||
override lateinit var setting: SettingsItem
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
|
||||
@ -14,8 +14,8 @@ import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: RunnableSetting
|
||||
SettingViewHolder<RunnableSetting>(binding.root, adapter) {
|
||||
override lateinit var setting: RunnableSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as RunnableSetting
|
||||
|
||||
@ -9,14 +9,17 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
abstract class SettingViewHolder(itemView: View, protected val adapter: SettingsAdapter) :
|
||||
abstract class SettingViewHolder<out T: SettingsItem>(itemView: View, protected val adapter: SettingsAdapter) :
|
||||
RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
itemView.setOnLongClickListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* The SettingsItem we are holding
|
||||
*/
|
||||
abstract val setting: T?
|
||||
/**
|
||||
* Called by the adapter to set this ViewHolder's child views to display the list item
|
||||
* it must now represent.
|
||||
|
||||
@ -12,8 +12,8 @@ import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSettin
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: SettingsItem
|
||||
SettingViewHolder<SettingsItem>(binding.root, adapter) {
|
||||
override lateinit var setting: SettingsItem
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item
|
||||
|
||||
@ -6,17 +6,14 @@ package org.citra.citra_emu.features.settings.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import org.citra.citra_emu.databinding.ListItemSettingBinding
|
||||
import org.citra.citra_emu.features.settings.model.AbstractFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.model.view.SliderSetting
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: SliderSetting
|
||||
SettingViewHolder<SliderSetting>(binding.root, adapter) {
|
||||
override lateinit var setting: SliderSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as SliderSetting
|
||||
@ -28,12 +25,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
binding.textSettingValue.text = when (setting.setting) {
|
||||
is ScaledFloatSetting ->
|
||||
"${(setting.setting as ScaledFloatSetting).float.toInt()}${setting.units}"
|
||||
is FloatSetting -> "${(setting.setting as AbstractFloatSetting).float}${setting.units}"
|
||||
else -> "${(setting.setting as AbstractIntSetting).int}${setting.units}"
|
||||
}
|
||||
binding.textSettingValue.text = "${setting.valueAsString}${setting.units}"
|
||||
|
||||
if (setting.isActive) {
|
||||
binding.textSettingName.alpha = 1f
|
||||
|
||||
@ -11,11 +11,10 @@ import org.citra.citra_emu.features.settings.model.view.StringInputSetting
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: SettingsItem
|
||||
|
||||
SettingViewHolder<StringInputSetting>(binding.root, adapter) {
|
||||
override lateinit var setting: StringInputSetting
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item
|
||||
setting = item as StringInputSetting
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
@ -24,7 +23,7 @@ class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: Settin
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
binding.textSettingValue.text = setting.setting?.valueAsString
|
||||
binding.textSettingValue.text = setting.selectedValue
|
||||
|
||||
if (setting.isActive) {
|
||||
binding.textSettingName.alpha = 1f
|
||||
@ -42,7 +41,7 @@ class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: Settin
|
||||
adapter.onClickDisabledSetting(!setting.isEditable)
|
||||
return
|
||||
}
|
||||
adapter.onStringInputClick((setting as StringInputSetting), bindingAdapterPosition)
|
||||
adapter.onStringInputClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
|
||||
@ -12,11 +12,11 @@ import org.citra.citra_emu.features.settings.model.view.SubmenuSetting
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var item: SubmenuSetting
|
||||
SettingViewHolder<SubmenuSetting>(binding.root, adapter) {
|
||||
override lateinit var setting: SubmenuSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
this.item = item as SubmenuSetting
|
||||
setting = item as SubmenuSetting
|
||||
if (item.iconId == 0) {
|
||||
binding.icon.visibility = View.GONE
|
||||
} else {
|
||||
@ -40,7 +40,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
adapter.onSubmenuClick(item)
|
||||
adapter.onSubmenuClick(setting)
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
|
||||
@ -12,9 +12,9 @@ import org.citra.citra_emu.features.settings.model.view.SwitchSetting
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
SettingViewHolder<SwitchSetting>(binding.root, adapter) {
|
||||
|
||||
private lateinit var setting: SwitchSetting
|
||||
override lateinit var setting: SwitchSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as SwitchSetting
|
||||
|
||||
@ -14,12 +14,9 @@ import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.SettingSection
|
||||
import org.citra.citra_emu.features.settings.model.Settings.SettingsSectionMap
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsActivityView
|
||||
import org.citra.citra_emu.utils.BiMap
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization.userDirectory
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.ini4j.Wini
|
||||
@ -27,7 +24,6 @@ import java.io.BufferedReader
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.util.TreeMap
|
||||
|
||||
|
||||
/**
|
||||
@ -36,40 +32,46 @@ import java.util.TreeMap
|
||||
object SettingsFile {
|
||||
const val FILE_NAME_CONFIG = "config"
|
||||
|
||||
private var sectionsMap = BiMap<String?, String?>()
|
||||
private val allSettings: List<AbstractSetting<*>> by lazy {
|
||||
BooleanSetting.values().toList() +
|
||||
IntSetting.values().toList() +
|
||||
FloatSetting.values().toList() +
|
||||
StringSetting.values().toList() +
|
||||
IntListSetting.values().toList()
|
||||
}
|
||||
|
||||
private fun findSettingByKey(key: String): AbstractSetting<*>? =
|
||||
allSettings.firstOrNull { it.key == key }
|
||||
/**
|
||||
* Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
|
||||
* effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
|
||||
* failed.
|
||||
* Reads a given .ini file from disk and updates a instance of the Settings class appropriately
|
||||
*
|
||||
* @param ini The ini file to load the settings from
|
||||
* @param settings The Settings instance to edit
|
||||
* @param isCustomGame
|
||||
* @param view The current view.
|
||||
* @return An Observable that emits a HashMap of the file's contents, then completes.
|
||||
*/
|
||||
fun readFile(
|
||||
ini: DocumentFile,
|
||||
settings: Settings,
|
||||
isCustomGame: Boolean,
|
||||
view: SettingsActivityView?
|
||||
): HashMap<String, SettingSection?> {
|
||||
val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
|
||||
) {
|
||||
var reader: BufferedReader? = null
|
||||
try {
|
||||
val context: Context = CitraApplication.appContext
|
||||
val inputStream = context.contentResolver.openInputStream(ini.uri)
|
||||
reader = BufferedReader(InputStreamReader(inputStream))
|
||||
var current: SettingSection? = null
|
||||
var currentSection: String? = null
|
||||
var line: String?
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
if (line!!.startsWith("[") && line!!.endsWith("]")) {
|
||||
current = sectionFromLine(line!!, isCustomGame)
|
||||
sections[current.name] = current
|
||||
} else if (current != null) {
|
||||
val setting = settingFromLine(line!!)
|
||||
if (setting != null) {
|
||||
current.putSetting(setting)
|
||||
}
|
||||
if (line!!.startsWith("[") && line.endsWith("]")) {
|
||||
currentSection = line.substring(1, line.length-1)
|
||||
} else if (currentSection != null) {
|
||||
val pair = parseLineToKeyValuePair(line) ?: continue
|
||||
val (key, rawValue) = pair
|
||||
val descriptor = findSettingByKey(key) ?: continue
|
||||
loadSettingInto(settings, descriptor, rawValue, isCustomGame)
|
||||
}
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
@ -87,102 +89,152 @@ object SettingsFile {
|
||||
}
|
||||
}
|
||||
}
|
||||
return sections
|
||||
}
|
||||
|
||||
fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
|
||||
return readFile(getSettingsFile(fileName), false, view)
|
||||
}
|
||||
|
||||
fun readFile(fileName: String): HashMap<String, SettingSection?> = readFile(fileName, null)
|
||||
|
||||
/**
|
||||
* Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
|
||||
* effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
|
||||
* failed.
|
||||
*
|
||||
* @param gameId the id of the game to load it's settings.
|
||||
* @param view The current view.
|
||||
* Load global settings from the config file into the settings instance
|
||||
*/
|
||||
fun readCustomGameSettings(
|
||||
gameId: String,
|
||||
view: SettingsActivityView?
|
||||
): HashMap<String, SettingSection?> {
|
||||
return readFile(getCustomGameSettingsFile(gameId), true, view)
|
||||
fun loadSettings(settings: Settings, view: SettingsActivityView? = null) {
|
||||
readFile(getSettingsFile(FILE_NAME_CONFIG),settings,false,view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
|
||||
* Load global settings AND custom settings into the settings instance, sets gameId
|
||||
*/
|
||||
fun loadSettings(settings: Settings, gameId: String, view: SettingsActivityView? = null) {
|
||||
settings.gameId = gameId
|
||||
loadSettings(settings, view)
|
||||
val file = findCustomGameSettingsFile(gameId) ?: return
|
||||
readFile(file, settings, true, view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the settings object to parse the raw string and store it in the correct map
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> loadSettingInto(
|
||||
settings: Settings,
|
||||
setting: AbstractSetting<T>,
|
||||
rawValue: String,
|
||||
isCustomGame: Boolean
|
||||
) {
|
||||
val value = setting.valueFromString(rawValue) ?: return
|
||||
if (isCustomGame) {
|
||||
settings.setOverride(setting, value)
|
||||
} else {
|
||||
settings.setGlobal(setting, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a the global settings from a Settings instance
|
||||
* to the global .ini file on disk. If unsuccessful, outputs an error
|
||||
* telling why it failed.
|
||||
*
|
||||
* @param fileName The target filename without a path or extension.
|
||||
* @param sections The HashMap containing the Settings we want to serialize.
|
||||
* @param settings The Settings instance we are saving
|
||||
* @param view The current view.
|
||||
*/
|
||||
fun saveFile(
|
||||
fileName: String,
|
||||
sections: TreeMap<String, SettingSection?>,
|
||||
view: SettingsActivityView
|
||||
fun saveGlobalFile(
|
||||
settings: Settings,
|
||||
view: SettingsActivityView? = null
|
||||
) {
|
||||
val ini = getSettingsFile(fileName)
|
||||
val ini = getSettingsFile(FILE_NAME_CONFIG)
|
||||
try {
|
||||
val context: Context = CitraApplication.appContext
|
||||
val inputStream = context.contentResolver.openInputStream(ini.uri)
|
||||
val writer = Wini(inputStream)
|
||||
val keySet: Set<String> = sections.keys
|
||||
for (key in keySet) {
|
||||
val section = sections[key]
|
||||
writeSection(writer, section!!)
|
||||
}
|
||||
inputStream!!.close()
|
||||
|
||||
for (setting in allSettings) {
|
||||
val value = settings.getGlobal(setting) ?: continue
|
||||
writeSettingToWini(writer, setting, value)
|
||||
}
|
||||
|
||||
val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt")
|
||||
writer.store(outputStream)
|
||||
outputStream!!.flush()
|
||||
outputStream.close()
|
||||
} catch (e: Exception) {
|
||||
Log.error("[SettingsFile] File not found: $fileName.ini: ${e.message}")
|
||||
view.showToastMessage(
|
||||
Log.error("[SettingsFile] File not found: $FILE_NAME_CONFIG.ini: ${e.message}")
|
||||
view?.showToastMessage(
|
||||
CitraApplication.appContext
|
||||
.getString(R.string.error_saving, fileName, e.message), false
|
||||
.getString(R.string.error_saving, FILE_NAME_CONFIG, e.message), false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveFile(
|
||||
fileName: String,
|
||||
setting: AbstractSetting
|
||||
/**
|
||||
* Save the per-game overrides to a per-game config file
|
||||
*/
|
||||
|
||||
fun saveCustomFile(
|
||||
settings: Settings,
|
||||
view: SettingsActivityView? = null
|
||||
) {
|
||||
val ini = getSettingsFile(fileName)
|
||||
if (!settings.isPerGame()) return
|
||||
val ini = getOrCreateCustomGameSettingsFile(settings.gameId!!)
|
||||
try {
|
||||
val context: Context = CitraApplication.appContext
|
||||
val writer = Wini()
|
||||
|
||||
val overrides = settings.getAllOverrides()
|
||||
for (descriptor in allSettings) {
|
||||
val value = overrides[descriptor.key] ?: continue
|
||||
writeSettingToWini(writer, descriptor, value)
|
||||
}
|
||||
|
||||
val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt")
|
||||
writer.store(outputStream)
|
||||
outputStream?.flush()
|
||||
outputStream?.close()
|
||||
} catch (e: Exception) {
|
||||
Log.error("[SettingsFile] Error saving custom file for ${settings.gameId}: ${e.message}")
|
||||
view?.onSettingsFileNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> saveSetting(setting: AbstractSetting<T>, settings: Settings) {
|
||||
if (settings.hasOverride(setting)) {
|
||||
// Currently a per-game setting, keep it that way
|
||||
val ini = getOrCreateCustomGameSettingsFile(settings.gameId!!)
|
||||
writeSingleSettingToFile(ini, setting, settings.get(setting))
|
||||
} else {
|
||||
// Currently global, save to global file
|
||||
val ini = getSettingsFile(FILE_NAME_CONFIG)
|
||||
writeSingleSettingToFile(ini, setting, settings.getGlobal(setting))
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> writeSingleSettingToFile(ini: DocumentFile, setting: AbstractSetting<T>, value: T) {
|
||||
try {
|
||||
val context = CitraApplication.appContext
|
||||
val inputStream = context.contentResolver.openInputStream(ini.uri)
|
||||
val writer = Wini(inputStream)
|
||||
writer.put(setting.section, setting.key, setting.valueAsString)
|
||||
inputStream!!.close()
|
||||
val writer = if (inputStream != null) Wini(inputStream) else Wini()
|
||||
inputStream?.close()
|
||||
writeSettingToWini(writer, setting, value as Any)
|
||||
val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt")
|
||||
writer.store(outputStream)
|
||||
outputStream!!.flush()
|
||||
outputStream.close()
|
||||
} catch (e: Exception) {
|
||||
Log.error("[SettingsFile] File not found: $fileName.ini: ${e.message}")
|
||||
Log.error("[SettingsFile] Error saving setting ${setting.key}: ${e.message}")
|
||||
}
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> writeSettingToWini(writer: Wini, descriptor: AbstractSetting<T>, value: Any) {
|
||||
val typedValue = value as T
|
||||
writer.put(descriptor.section, descriptor.key, descriptor.valueToString(typedValue))
|
||||
}
|
||||
|
||||
private fun mapSectionNameFromIni(generalSectionName: String): String? {
|
||||
return if (sectionsMap.getForward(generalSectionName) != null) {
|
||||
sectionsMap.getForward(generalSectionName)
|
||||
} else {
|
||||
generalSectionName
|
||||
}
|
||||
private fun parseLineToKeyValuePair(line: String): Pair<String, String>? {
|
||||
val splitLine = line.split("=".toRegex(), limit = 2)
|
||||
if (splitLine.size != 2) return null
|
||||
val key = splitLine[0].trim()
|
||||
val value = splitLine[1].trim()
|
||||
if (value.isEmpty()) return null
|
||||
return Pair(key, value)
|
||||
}
|
||||
|
||||
private fun mapSectionNameToIni(generalSectionName: String): String {
|
||||
return if (sectionsMap.getBackward(generalSectionName) != null) {
|
||||
sectionsMap.getBackward(generalSectionName).toString()
|
||||
} else {
|
||||
generalSectionName
|
||||
}
|
||||
}
|
||||
|
||||
fun getSettingsFile(fileName: String): DocumentFile {
|
||||
val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory))
|
||||
@ -190,96 +242,20 @@ object SettingsFile {
|
||||
return configDirectory!!.findFile("$fileName.ini")!!
|
||||
}
|
||||
|
||||
private fun getCustomGameSettingsFile(gameId: String): DocumentFile {
|
||||
fun customExists(gameId: String): Boolean = findCustomGameSettingsFile(gameId) != null
|
||||
|
||||
private fun findCustomGameSettingsFile(gameId: String): DocumentFile? {
|
||||
val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory))
|
||||
val configDirectory = root!!.findFile("GameSettings")
|
||||
return configDirectory!!.findFile("$gameId.ini")!!
|
||||
val configDir = root?.findFile("config") ?: return null
|
||||
val customDir = configDir.findFile("custom") ?: return null
|
||||
return customDir.findFile("$gameId.ini")
|
||||
}
|
||||
|
||||
private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
|
||||
var sectionName: String = line.substring(1, line.length - 1)
|
||||
if (isCustomGame) {
|
||||
sectionName = mapSectionNameToIni(sectionName)
|
||||
}
|
||||
return SettingSection(sectionName)
|
||||
}
|
||||
|
||||
/**
|
||||
* For a line of text, determines what type of data is being represented, and returns
|
||||
* a Setting object containing this data.
|
||||
*
|
||||
* @param line The line of text being parsed.
|
||||
* @return A typed Setting containing the key/value contained in the line.
|
||||
*/
|
||||
private fun settingFromLine(line: String): AbstractSetting? {
|
||||
val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (splitLine.size != 2) {
|
||||
return null
|
||||
}
|
||||
val key = splitLine[0].trim { it <= ' ' }
|
||||
val value = splitLine[1].trim { it <= ' ' }
|
||||
if (value.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val booleanSetting = BooleanSetting.from(key)
|
||||
if (booleanSetting != null) {
|
||||
booleanSetting.boolean = value.toBoolean()
|
||||
return booleanSetting
|
||||
}
|
||||
|
||||
val intSetting = IntSetting.from(key)
|
||||
if (intSetting != null) {
|
||||
try {
|
||||
intSetting.int = value.toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
intSetting.int = if (value.toBoolean()) 1 else 0
|
||||
}
|
||||
return intSetting
|
||||
}
|
||||
|
||||
val scaledFloatSetting = ScaledFloatSetting.from(key)
|
||||
if (scaledFloatSetting != null) {
|
||||
scaledFloatSetting.float = value.toFloat() * scaledFloatSetting.scale
|
||||
return scaledFloatSetting
|
||||
}
|
||||
|
||||
val floatSetting = FloatSetting.from(key)
|
||||
if (floatSetting != null) {
|
||||
floatSetting.float = value.toFloat()
|
||||
return floatSetting
|
||||
}
|
||||
|
||||
val stringSetting = StringSetting.from(key)
|
||||
if (stringSetting != null) {
|
||||
stringSetting.string = value
|
||||
return stringSetting
|
||||
}
|
||||
|
||||
val intListSetting = IntListSetting.from(key)
|
||||
if (intListSetting != null) {
|
||||
intListSetting.list = value.split(", ").map { it.toInt() }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the contents of a Section HashMap to disk.
|
||||
*
|
||||
* @param parser A Wini pointed at a file on disk.
|
||||
* @param section A section containing settings to be written to the file.
|
||||
*/
|
||||
private fun writeSection(parser: Wini, section: SettingSection) {
|
||||
// Write the section header.
|
||||
val header = section.name
|
||||
|
||||
// Write this section's values.
|
||||
val settings = section.settings
|
||||
val keySet: Set<String> = settings.keys
|
||||
for (key in keySet) {
|
||||
val setting = settings[key]
|
||||
parser.put(header, setting!!.key, setting.valueAsString)
|
||||
}
|
||||
private fun getOrCreateCustomGameSettingsFile(gameId: String): DocumentFile {
|
||||
val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory))!!
|
||||
val configDir = root.findFile("config") ?: root.createDirectory("config")!!
|
||||
val customDir = configDir.findFile("custom") ?: configDir.createDirectory("custom")!!
|
||||
return customDir.findFile("$gameId.ini")
|
||||
?: customDir.createFile("*/*", "$gameId.ini")!!
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.Insets
|
||||
@ -44,7 +45,6 @@ import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
@ -70,7 +70,6 @@ import org.citra.citra_emu.display.ScreenLayout
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.model.Game
|
||||
@ -78,7 +77,6 @@ import org.citra.citra_emu.utils.BuildUtil
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.GameHelper
|
||||
import org.citra.citra_emu.utils.GameIconUtils
|
||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||
@ -104,8 +102,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||
|
||||
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
private val settings get() = settingsViewModel.settings
|
||||
|
||||
private val onPause = Runnable{ togglePause() }
|
||||
private val onShutdown = Runnable{ emulationState.stop() }
|
||||
@ -184,7 +180,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
retainInstance = true
|
||||
emulationState = EmulationState(game.path)
|
||||
emulationActivity = requireActivity() as EmulationActivity
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settings)
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, Settings.settings)
|
||||
EmulationLifecycleUtil.addPauseResumeHook(onPause)
|
||||
EmulationLifecycleUtil.addShutdownHook(onShutdown)
|
||||
}
|
||||
@ -211,7 +207,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
if (requireActivity().isFinishing) {
|
||||
return
|
||||
}
|
||||
|
||||
binding.surfaceInputOverlay.initializeSettings(Settings.settings)
|
||||
binding.surfaceEmulation.holder.addCallback(this)
|
||||
binding.doneControlConfig.setOnClickListener {
|
||||
binding.doneControlConfig.visibility = View.GONE
|
||||
@ -221,7 +217,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
// Show/hide the "Stats" overlay
|
||||
updateShowPerformanceOverlay()
|
||||
|
||||
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
|
||||
val position = Settings.settings.get(IntSetting.PERFORMANCE_OVERLAY_POSITION)
|
||||
updateStatsPosition(position)
|
||||
|
||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
@ -382,10 +378,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
R.id.menu_settings -> {
|
||||
SettingsActivity.launch(
|
||||
requireContext(),
|
||||
SettingsFile.FILE_NAME_CONFIG,
|
||||
""
|
||||
SettingsFile.FILE_NAME_CONFIG, null
|
||||
)
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@ -508,7 +502,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
emulationState.unpause()
|
||||
|
||||
// If the overlay is enabled, we need to update the position if changed
|
||||
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
|
||||
val position = Settings.settings.get(IntSetting.PERFORMANCE_OVERLAY_POSITION)
|
||||
updateStatsPosition(position)
|
||||
|
||||
binding.inGameMenu.menu.findItem(R.id.menu_emulation_pause)?.let { menuItem ->
|
||||
@ -707,7 +701,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
popupMenu.menu.apply {
|
||||
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
|
||||
findItem(R.id.menu_performance_overlay_show).isChecked =
|
||||
BooleanSetting.PERF_OVERLAY_ENABLE.boolean
|
||||
Settings.settings.get(BooleanSetting.PERF_OVERLAY_ENABLE)
|
||||
findItem(R.id.menu_haptic_feedback).isChecked = EmulationMenuSettings.hapticFeedback
|
||||
findItem(R.id.menu_emulation_joystick_rel_center).isChecked =
|
||||
EmulationMenuSettings.joystickRelCenter
|
||||
@ -724,8 +718,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
}
|
||||
|
||||
R.id.menu_performance_overlay_show -> {
|
||||
BooleanSetting.PERF_OVERLAY_ENABLE.boolean = !BooleanSetting.PERF_OVERLAY_ENABLE.boolean
|
||||
settings.saveSetting(BooleanSetting.PERF_OVERLAY_ENABLE, SettingsFile.FILE_NAME_CONFIG)
|
||||
Settings.settings.update(BooleanSetting.PERF_OVERLAY_ENABLE,
|
||||
Settings.settings.get(BooleanSetting.PERF_OVERLAY_ENABLE))
|
||||
SettingsFile.saveSetting(BooleanSetting.PERF_OVERLAY_ENABLE, Settings.settings)
|
||||
updateShowPerformanceOverlay()
|
||||
true
|
||||
}
|
||||
@ -921,7 +916,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
|
||||
popupMenu.menuInflater.inflate(R.menu.menu_landscape_screen_layout, popupMenu.menu)
|
||||
|
||||
val layoutOptionMenuItem = when (IntSetting.SCREEN_LAYOUT.int) {
|
||||
val layoutOptionMenuItem = when (Settings.settings.get(IntSetting.SCREEN_LAYOUT)) {
|
||||
ScreenLayout.ORIGINAL.int ->
|
||||
R.id.menu_screen_layout_original
|
||||
|
||||
@ -993,7 +988,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
|
||||
popupMenu.menuInflater.inflate(R.menu.menu_portrait_screen_layout, popupMenu.menu)
|
||||
|
||||
val layoutOptionMenuItem = when (IntSetting.PORTRAIT_SCREEN_LAYOUT.int) {
|
||||
val layoutOptionMenuItem = when (Settings.settings.get(IntSetting.PORTRAIT_SCREEN_LAYOUT)) {
|
||||
PortraitScreenLayout.TOP_FULL_WIDTH.int ->
|
||||
R.id.menu_portrait_layout_top_full
|
||||
PortraitScreenLayout.ORIGINAL.int ->
|
||||
@ -1260,7 +1255,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
|
||||
if (BooleanSetting.PERF_OVERLAY_ENABLE.boolean) {
|
||||
if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_ENABLE)) {
|
||||
val SYSTEM_FPS = 0
|
||||
val FPS = 1
|
||||
val SPEED = 2
|
||||
@ -1275,11 +1270,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
val perfStats = NativeLibrary.getPerfStats()
|
||||
val dividerString = "\u00A0\u2502 "
|
||||
if (perfStats[FPS] > 0) {
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_FPS.boolean) {
|
||||
if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_FPS)) {
|
||||
sb.append(String.format("FPS:\u00A0%d", (perfStats[FPS] + 0.5).toInt()))
|
||||
}
|
||||
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME.boolean) {
|
||||
if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_FRAMETIME)) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
sb.append(
|
||||
String.format(
|
||||
@ -1294,7 +1289,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
)
|
||||
}
|
||||
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_SPEED.boolean) {
|
||||
if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_SPEED)) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
sb.append(
|
||||
String.format(
|
||||
@ -1304,14 +1299,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
)
|
||||
}
|
||||
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE.boolean) {
|
||||
if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE)) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
val appRamUsage =
|
||||
File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 / 1000000
|
||||
sb.append("Process\u00A0RAM:\u00A0$appRamUsage\u00A0MB")
|
||||
}
|
||||
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM.boolean) {
|
||||
if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_AVAILABLE_RAM)) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
context?.let { ctx ->
|
||||
val activityManager =
|
||||
@ -1324,14 +1319,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
}
|
||||
}
|
||||
|
||||
if (BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP.boolean) {
|
||||
if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_SHOW_BATTERY_TEMP)) {
|
||||
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||
val batteryTemp = getBatteryTemperature()
|
||||
val tempF = celsiusToFahrenheit(batteryTemp)
|
||||
sb.append(String.format("%.1f°C/%.1f°F", batteryTemp, tempF))
|
||||
}
|
||||
|
||||
if (BooleanSetting.PERF_OVERLAY_BACKGROUND.boolean) {
|
||||
if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_BACKGROUND)) {
|
||||
binding.performanceOverlayShowText.setBackgroundResource(R.color.citra_transparent_black)
|
||||
} else {
|
||||
binding.performanceOverlayShowText.setBackgroundResource(0)
|
||||
|
||||
@ -24,6 +24,7 @@ import androidx.preference.PreferenceManager
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
import org.citra.citra_emu.utils.TurboHelper
|
||||
import java.lang.NullPointerException
|
||||
@ -45,7 +46,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
|
||||
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
|
||||
private val settingsViewModel = NativeLibrary.sEmulationActivity.get()!!.settingsViewModel
|
||||
private lateinit var settings: Settings
|
||||
|
||||
// Stores the ID of the pointer that interacted with the 3DS touchscreen.
|
||||
private var touchscreenPointerId = -1
|
||||
@ -71,6 +72,10 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||
requestFocus()
|
||||
}
|
||||
|
||||
fun initializeSettings(settings: Settings) {
|
||||
this.settings = settings
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
super.draw(canvas)
|
||||
overlayButtons.forEach { it.draw(canvas) }
|
||||
@ -173,7 +178,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||
swapScreen()
|
||||
}
|
||||
else if (button.id == NativeLibrary.ButtonType.BUTTON_TURBO && button.status == NativeLibrary.ButtonState.PRESSED) {
|
||||
TurboHelper.toggleTurbo(true)
|
||||
TurboHelper.toggleTurbo(true, settings)
|
||||
}
|
||||
|
||||
NativeLibrary.onGamePadEvent(
|
||||
|
||||
@ -48,7 +48,6 @@ import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.contracts.OpenFileResultContract
|
||||
import org.citra.citra_emu.databinding.ActivityMainBinding
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.fragments.GrantMissingFilesystemPermissionFragment
|
||||
@ -72,7 +71,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
private val homeViewModel: HomeViewModel by viewModels()
|
||||
private val gamesViewModel: GamesViewModel by viewModels()
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
override var themeId: Int = 0
|
||||
|
||||
@ -95,7 +93,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
if (PermissionsHandler.hasWriteAccess(applicationContext) &&
|
||||
DirectoryInitialization.areCitraDirectoriesReady() &&
|
||||
!CitraDirectoryUtils.needToUpdateManually()) {
|
||||
settingsViewModel.settings.loadSettings()
|
||||
// load the global settings from the config file at program launch
|
||||
SettingsFile.loadSettings(Settings.settings)
|
||||
}
|
||||
|
||||
ThemeUtil.ThemeChangeListener(this)
|
||||
|
||||
@ -9,6 +9,7 @@ import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
object TurboHelper {
|
||||
private var turboSpeedEnabled = false
|
||||
@ -17,12 +18,12 @@ object TurboHelper {
|
||||
return turboSpeedEnabled
|
||||
}
|
||||
|
||||
fun reloadTurbo(showToast: Boolean) {
|
||||
fun reloadTurbo(showToast: Boolean, settings: Settings) {
|
||||
val context = CitraApplication.appContext
|
||||
val toastMessage: String
|
||||
|
||||
if (turboSpeedEnabled) {
|
||||
NativeLibrary.setTemporaryFrameLimit(IntSetting.TURBO_LIMIT.int.toDouble())
|
||||
NativeLibrary.setTemporaryFrameLimit(settings.get(IntSetting.TURBO_LIMIT).toDouble())
|
||||
toastMessage = context.getString(R.string.turbo_enabled_toast)
|
||||
} else {
|
||||
NativeLibrary.disableTemporaryFrameLimit()
|
||||
@ -34,12 +35,12 @@ object TurboHelper {
|
||||
}
|
||||
}
|
||||
|
||||
fun setTurboEnabled(state: Boolean, showToast: Boolean) {
|
||||
fun setTurboEnabled(state: Boolean, showToast: Boolean, settings: Settings) {
|
||||
turboSpeedEnabled = state
|
||||
reloadTurbo(showToast)
|
||||
reloadTurbo(showToast, settings)
|
||||
}
|
||||
|
||||
fun toggleTurbo(showToast: Boolean) {
|
||||
setTurboEnabled(!TurboHelper.isTurboSpeedEnabled(), showToast)
|
||||
fun toggleTurbo(showToast: Boolean, settings: Settings) {
|
||||
setTurboEnabled(!TurboHelper.isTurboSpeedEnabled(), showToast, settings)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -7,9 +7,13 @@ package org.citra.citra_emu.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class EmulationViewModel : ViewModel() {
|
||||
val emulationStarted get() = _emulationStarted.asStateFlow()
|
||||
// convenience shortcut for
|
||||
val settings = Settings.settings
|
||||
|
||||
private val _emulationStarted = MutableStateFlow(false)
|
||||
|
||||
val shaderProgress get() = _shaderProgress.asStateFlow()
|
||||
@ -21,6 +25,7 @@ class EmulationViewModel : ViewModel() {
|
||||
val shaderMessage get() = _shaderMessage.asStateFlow()
|
||||
private val _shaderMessage = MutableStateFlow("")
|
||||
|
||||
|
||||
fun setShaderProgress(progress: Int) {
|
||||
_shaderProgress.value = progress
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user