mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-06-01 12:15:03 -06:00
Merge 397730e7ee into c650473fdc
This commit is contained in:
commit
f392e87283
@ -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.hotkeys.HotkeyUtility
|
||||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
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.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.features.settings.model.view.InputBindingSetting
|
||||||
import org.citra.citra_emu.fragments.EmulationFragment
|
import org.citra.citra_emu.fragments.EmulationFragment
|
||||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
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.RefreshRateUtil
|
||||||
import org.citra.citra_emu.utils.ThemeUtil
|
import org.citra.citra_emu.utils.ThemeUtil
|
||||||
import org.citra.citra_emu.viewmodel.EmulationViewModel
|
import org.citra.citra_emu.viewmodel.EmulationViewModel
|
||||||
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
|
|
||||||
class EmulationActivity : AppCompatActivity() {
|
class EmulationActivity : AppCompatActivity() {
|
||||||
private val preferences: SharedPreferences
|
private val preferences: SharedPreferences
|
||||||
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||||
var isActivityRecreated = false
|
var isActivityRecreated = false
|
||||||
private val emulationViewModel: EmulationViewModel by viewModels()
|
val emulationViewModel: EmulationViewModel by viewModels()
|
||||||
val settingsViewModel: SettingsViewModel by viewModels()
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityEmulationBinding
|
private lateinit var binding: ActivityEmulationBinding
|
||||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||||
private lateinit var hotkeyUtility: HotkeyUtility
|
private lateinit var hotkeyUtility: HotkeyUtility
|
||||||
@ -88,14 +87,32 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
RefreshRateUtil.enforceRefreshRate(this, sixtyHz = true)
|
RefreshRateUtil.enforceRefreshRate(this, sixtyHz = true)
|
||||||
|
|
||||||
ThemeUtil.setTheme(this)
|
ThemeUtil.setTheme(this)
|
||||||
settingsViewModel.settings.loadSettings()
|
val game = try {
|
||||||
|
intent.extras?.let { extras ->
|
||||||
|
BundleCompat.getParcelable(extras, "game", Game::class.java)
|
||||||
|
} ?: run {
|
||||||
|
Log.error("[EmulationActivity] Missing game data in intent extras")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.error("[EmulationActivity] Failed to retrieve game data: ${e.message}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// load global settings if for some reason they aren't (should be loaded in MainActivity)
|
||||||
|
if (Settings.settings.getAllGlobal().isEmpty()) {
|
||||||
|
SettingsFile.loadSettings(Settings.settings)
|
||||||
|
}
|
||||||
|
// load per-game settings
|
||||||
|
SettingsFile.loadSettings(Settings.settings, String.format("%016X", game.titleId))
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
secondaryDisplay = SecondaryDisplay(this)
|
|
||||||
|
secondaryDisplay = SecondaryDisplay(this, Settings.settings)
|
||||||
secondaryDisplay.updateDisplay()
|
secondaryDisplay.updateDisplay()
|
||||||
|
|
||||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, Settings.settings)
|
||||||
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
|
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this, Settings.settings)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
@ -121,18 +138,6 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
applyOrientationSettings() // Check for orientation settings at startup
|
applyOrientationSettings() // Check for orientation settings at startup
|
||||||
|
|
||||||
val game = try {
|
|
||||||
intent.extras?.let { extras ->
|
|
||||||
BundleCompat.getParcelable(extras, "game", Game::class.java)
|
|
||||||
} ?: run {
|
|
||||||
Log.error("[EmulationActivity] Missing game data in intent extras")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.error("[EmulationActivity] Failed to retrieve game data: ${e.message}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeLibrary.playTimeManagerStart(game.titleId)
|
NativeLibrary.playTimeManagerStart(game.titleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +147,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
enableFullscreenImmersive()
|
enableFullscreenImmersive()
|
||||||
applyOrientationSettings() // Check for orientation settings changes on runtime
|
applyOrientationSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
@ -179,6 +184,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
secondaryDisplay.releasePresentation()
|
secondaryDisplay.releasePresentation()
|
||||||
secondaryDisplay.releaseVD()
|
secondaryDisplay.releaseVD()
|
||||||
|
|
||||||
|
Settings.settings.removePerGameSettings()
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,11 +236,11 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableFullscreenImmersive() {
|
fun enableFullscreenImmersive() {
|
||||||
val attributes = window.attributes
|
val attributes = window.attributes
|
||||||
|
|
||||||
attributes.layoutInDisplayCutoutMode =
|
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
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||||
} else {
|
} else {
|
||||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
||||||
@ -250,8 +257,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyOrientationSettings() {
|
fun applyOrientationSettings() {
|
||||||
val orientationOption = IntSetting.ORIENTATION_OPTION.int
|
val orientationOption = Settings.settings.get(IntSetting.ORIENTATION_OPTION)
|
||||||
screenAdjustmentUtil.changeActivityOrientation(orientationOption)
|
screenAdjustmentUtil.changeActivityOrientation(orientationOption)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,8 @@ import org.citra.citra_emu.utils.FileUtil
|
|||||||
import org.citra.citra_emu.utils.GameIconUtils
|
import org.citra.citra_emu.utils.GameIconUtils
|
||||||
import org.citra.citra_emu.utils.Log
|
import org.citra.citra_emu.utils.Log
|
||||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||||
|
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||||
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
|
|
||||||
class GameAdapter(
|
class GameAdapter(
|
||||||
private val activity: AppCompatActivity,
|
private val activity: AppCompatActivity,
|
||||||
@ -485,6 +487,15 @@ class GameAdapter(
|
|||||||
bottomSheetDialog.dismiss()
|
bottomSheetDialog.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bottomSheetView.findViewById<MaterialButton>(R.id.application_settings).setOnClickListener {
|
||||||
|
SettingsActivity.launch(
|
||||||
|
context,
|
||||||
|
SettingsFile.FILE_NAME_CONFIG,
|
||||||
|
String.format("%016X", holder.game.titleId)
|
||||||
|
)
|
||||||
|
bottomSheetDialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
|
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
|
||||||
if (game.isInstalled) {
|
if (game.isInstalled) {
|
||||||
compressDecompressButton.setOnClickListener {
|
compressDecompressButton.setOnClickListener {
|
||||||
|
|||||||
@ -29,13 +29,13 @@ class ScreenAdjustmentUtil(
|
|||||||
isEnabled,
|
isEnabled,
|
||||||
windowManager.defaultDisplay.rotation
|
windowManager.defaultDisplay.rotation
|
||||||
)
|
)
|
||||||
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
|
settings.update(BooleanSetting.SWAP_SCREEN, isEnabled)
|
||||||
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
SettingsFile.saveSetting(BooleanSetting.SWAP_SCREEN, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cycleLayouts() {
|
fun cycleLayouts() {
|
||||||
|
|
||||||
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
|
val landscapeLayoutsToCycle = settings.get(IntListSetting.LAYOUTS_TO_CYCLE)
|
||||||
val landscapeValues =
|
val landscapeValues =
|
||||||
if (landscapeLayoutsToCycle.isNotEmpty())
|
if (landscapeLayoutsToCycle.isNotEmpty())
|
||||||
landscapeLayoutsToCycle.toIntArray()
|
landscapeLayoutsToCycle.toIntArray()
|
||||||
@ -45,12 +45,12 @@ class ScreenAdjustmentUtil(
|
|||||||
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
|
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
|
||||||
|
|
||||||
if (NativeLibrary.isPortraitMode) {
|
if (NativeLibrary.isPortraitMode) {
|
||||||
val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int
|
val currentLayout = settings.get(IntSetting.PORTRAIT_SCREEN_LAYOUT)
|
||||||
val pos = portraitValues.indexOf(currentLayout)
|
val pos = portraitValues.indexOf(currentLayout)
|
||||||
val layoutOption = portraitValues[(pos + 1) % portraitValues.size]
|
val layoutOption = portraitValues[(pos + 1) % portraitValues.size]
|
||||||
changePortraitOrientation(layoutOption)
|
changePortraitOrientation(layoutOption)
|
||||||
} else {
|
} else {
|
||||||
val currentLayout = IntSetting.SCREEN_LAYOUT.int
|
val currentLayout = settings.get(IntSetting.SCREEN_LAYOUT)
|
||||||
val pos = landscapeValues.indexOf(currentLayout)
|
val pos = landscapeValues.indexOf(currentLayout)
|
||||||
val layoutOption = landscapeValues[(pos + 1) % landscapeValues.size]
|
val layoutOption = landscapeValues[(pos + 1) % landscapeValues.size]
|
||||||
changeScreenOrientation(layoutOption)
|
changeScreenOrientation(layoutOption)
|
||||||
@ -58,30 +58,30 @@ class ScreenAdjustmentUtil(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun changePortraitOrientation(layoutOption: Int) {
|
fun changePortraitOrientation(layoutOption: Int) {
|
||||||
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
|
settings.update(IntSetting.PORTRAIT_SCREEN_LAYOUT, layoutOption)
|
||||||
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
SettingsFile.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, settings)
|
||||||
NativeLibrary.reloadSettings()
|
NativeLibrary.reloadSettings()
|
||||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeScreenOrientation(layoutOption: Int) {
|
fun changeScreenOrientation(layoutOption: Int) {
|
||||||
IntSetting.SCREEN_LAYOUT.int = layoutOption
|
settings.update(IntSetting.SCREEN_LAYOUT, layoutOption)
|
||||||
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
SettingsFile.saveSetting(IntSetting.SCREEN_LAYOUT, settings)
|
||||||
NativeLibrary.reloadSettings()
|
NativeLibrary.reloadSettings()
|
||||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeActivityOrientation(orientationOption: Int) {
|
fun changeActivityOrientation(orientationOption: Int) {
|
||||||
val activity = context as? Activity ?: return
|
val activity = context as? Activity ?: return
|
||||||
IntSetting.ORIENTATION_OPTION.int = orientationOption
|
settings.update(IntSetting.ORIENTATION_OPTION, orientationOption)
|
||||||
settings.saveSetting(IntSetting.ORIENTATION_OPTION, SettingsFile.FILE_NAME_CONFIG)
|
SettingsFile.saveSetting(IntSetting.ORIENTATION_OPTION, settings)
|
||||||
activity.requestedOrientation = orientationOption
|
activity.requestedOrientation = orientationOption
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleScreenUpright() {
|
fun toggleScreenUpright() {
|
||||||
val uprightBoolean = BooleanSetting.UPRIGHT_SCREEN.boolean
|
val uprightBoolean = settings.get(BooleanSetting.UPRIGHT_SCREEN)
|
||||||
BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean
|
settings.update(BooleanSetting.UPRIGHT_SCREEN, !uprightBoolean)
|
||||||
settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
SettingsFile.saveSetting(BooleanSetting.UPRIGHT_SCREEN, settings)
|
||||||
NativeLibrary.reloadSettings()
|
NativeLibrary.reloadSettings()
|
||||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||||
|
|
||||||
|
|||||||
@ -16,8 +16,9 @@ import android.view.SurfaceView
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||||
import org.citra.citra_emu.NativeLibrary
|
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 var pres: SecondaryDisplayPresentation? = null
|
||||||
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
private val vd: VirtualDisplay
|
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
|
// decide if we are going to the external display or the internal one
|
||||||
var display = getExternalDisplay(context)
|
var display = getExternalDisplay(context)
|
||||||
if (display == null ||
|
if (display == null || settings.get(IntSetting.SECONDARY_DISPLAY_LAYOUT) == SecondaryDisplayLayout.NONE.int) {
|
||||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
|
|
||||||
display = vd.display
|
display = vd.display
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,8 @@ import org.citra.citra_emu.features.settings.model.Settings
|
|||||||
|
|
||||||
class HotkeyUtility(
|
class HotkeyUtility(
|
||||||
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
|
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
|
||||||
private val context: Context
|
private val context: Context,
|
||||||
|
private val settings: Settings
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val hotkeyButtons = Hotkey.entries.map { it.button }
|
private val hotkeyButtons = Hotkey.entries.map { it.button }
|
||||||
@ -112,7 +113,7 @@ class HotkeyUtility(
|
|||||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
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 -> {
|
Hotkey.QUICKSAVE.button -> {
|
||||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||||
Toast.makeText(
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
interface AbstractSetting {
|
interface AbstractSetting<T> {
|
||||||
val key: String?
|
val key: String
|
||||||
val section: String?
|
val section: String
|
||||||
|
val defaultValue: T
|
||||||
val isRuntimeEditable: Boolean
|
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 key: String,
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: Boolean
|
override val defaultValue: Boolean
|
||||||
) : AbstractBooleanSetting {
|
) : AbstractSetting<Boolean> {
|
||||||
EXPAND_TO_CUTOUT_AREA(SettingKeys.expand_to_cutout_area(), Settings.SECTION_LAYOUT, false),
|
EXPAND_TO_CUTOUT_AREA(SettingKeys.expand_to_cutout_area(), Settings.SECTION_LAYOUT, false),
|
||||||
SPIRV_SHADER_GEN(SettingKeys.spirv_shader_gen(), Settings.SECTION_RENDERER, true),
|
SPIRV_SHADER_GEN(SettingKeys.spirv_shader_gen(), Settings.SECTION_RENDERER, true),
|
||||||
ASYNC_SHADERS(SettingKeys.async_shader_compilation(), Settings.SECTION_RENDERER, false),
|
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),
|
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
|
||||||
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false);
|
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false);
|
||||||
|
|
||||||
override var boolean: Boolean = defaultValue
|
override fun valueFromString(string: String): Boolean? {
|
||||||
|
return when (string.trim().lowercase()) {
|
||||||
override val valueAsString: String
|
"1", "true" -> true
|
||||||
get() = boolean.toString()
|
"0", "false" -> false
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val isRuntimeEditable: Boolean
|
override val isRuntimeEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
@ -98,7 +101,5 @@ enum class BooleanSetting(
|
|||||||
|
|
||||||
fun from(key: String): BooleanSetting? =
|
fun from(key: String): BooleanSetting? =
|
||||||
BooleanSetting.values().firstOrNull { it.key == key }
|
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(
|
enum class FloatSetting(
|
||||||
override val key: String,
|
override val key: String,
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: Float
|
override val defaultValue: Float,
|
||||||
) : AbstractFloatSetting {
|
val scale:Int = 1
|
||||||
|
) : AbstractSetting<Float> {
|
||||||
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
|
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
|
||||||
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
|
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
|
||||||
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
|
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f, 255),
|
||||||
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
|
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f, 255),
|
||||||
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);
|
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
|
// valueToString scales back down to raw for file
|
||||||
get() = float.toString()
|
override fun valueToString(value: Float): String {
|
||||||
|
return (value / scale).toString()
|
||||||
|
}
|
||||||
|
|
||||||
override val isRuntimeEditable: Boolean
|
override val isRuntimeEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
@ -36,7 +43,5 @@ enum class FloatSetting(
|
|||||||
private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
|
private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
|
||||||
|
|
||||||
fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
|
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 section: String,
|
||||||
override val defaultValue: List<Int>,
|
override val defaultValue: List<Int>,
|
||||||
val canBeEmpty: Boolean = true
|
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);
|
LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
|
||||||
|
|
||||||
private var backingList: List<Int> = defaultValue
|
override fun valueToString(value: List<Int>): String = value.joinToString()
|
||||||
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 valueFromString(string: String): List<Int>? {
|
||||||
|
return string.split(",")
|
||||||
|
.mapNotNull { it.trim().toIntOrNull() }
|
||||||
|
.takeIf { canBeEmpty || it.isNotEmpty() }
|
||||||
|
}
|
||||||
override val isRuntimeEditable: Boolean
|
override val isRuntimeEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
for (setting in NOT_RUNTIME_EDITABLE) {
|
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||||
@ -46,7 +35,5 @@ enum class IntListSetting(
|
|||||||
|
|
||||||
fun from(key: String): IntListSetting? =
|
fun from(key: String): IntListSetting? =
|
||||||
values().firstOrNull { it.key == key }
|
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 key: String,
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: Int
|
override val defaultValue: Int
|
||||||
) : AbstractIntSetting {
|
) : AbstractSetting<Int> {
|
||||||
FRAME_LIMIT(SettingKeys.frame_limit(), Settings.SECTION_RENDERER, 100),
|
FRAME_LIMIT(SettingKeys.frame_limit(), Settings.SECTION_RENDERER, 100),
|
||||||
EMULATED_REGION(SettingKeys.region_value(), Settings.SECTION_SYSTEM, -1),
|
EMULATED_REGION(SettingKeys.region_value(), Settings.SECTION_SYSTEM, -1),
|
||||||
INIT_CLOCK(SettingKeys.init_clock(), Settings.SECTION_SYSTEM, 0),
|
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),
|
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
|
||||||
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
|
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
|
||||||
TEXTURE_SAMPLING(SettingKeys.texture_sampling(), 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),
|
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
|
||||||
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
|
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
|
||||||
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
|
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),
|
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0),
|
||||||
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
|
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
|
||||||
|
|
||||||
override var int: Int = defaultValue
|
|
||||||
|
|
||||||
override val valueAsString: String
|
override fun valueFromString(string: String): Int? {
|
||||||
get() = int.toString()
|
return string.toIntOrNull() ?: when (string.trim().lowercase()) {
|
||||||
|
"true" -> 1
|
||||||
|
"false" -> 0
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override val isRuntimeEditable: Boolean
|
override val isRuntimeEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
@ -83,6 +87,5 @@ enum class IntSetting(
|
|||||||
|
|
||||||
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
|
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
|
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.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 {
|
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
|
* Updates an existing setting honoring whether it is *currently* global or local. This will
|
||||||
* when getting a key not already in the map
|
* be used by the Quick Menu
|
||||||
*/
|
*/
|
||||||
class SettingsSectionMap : HashMap<String, SettingSection?>() {
|
fun <T> update(setting: AbstractSetting<T>, value: T) {
|
||||||
override operator fun get(key: String): SettingSection? {
|
if (hasOverride(setting)) setOverride(setting, value) else setGlobal(setting, value)
|
||||||
if (!super.containsKey(key)) {
|
}
|
||||||
val section = SettingSection(key)
|
|
||||||
super.put(key, section)
|
/** Merge the globals from other into the current settings. Merge per-game if game id is the same. */
|
||||||
return section
|
fun mergeSettings(other: Settings) {
|
||||||
}
|
other.globalValues.forEach{ (key, value) ->
|
||||||
return super.get(key)
|
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 <T> clearOverride(setting: AbstractSetting<T>) {
|
||||||
|
perGameOverrides.remove(setting.key)
|
||||||
fun getSection(sectionName: String): SettingSection? {
|
|
||||||
return sections[sectionName]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isEmpty: Boolean
|
fun hasOverride(setting: AbstractSetting<*>): Boolean {
|
||||||
get() = sections.isEmpty()
|
return perGameOverrides.containsKey(setting.key)
|
||||||
|
|
||||||
fun loadSettings(view: SettingsActivityView? = null) {
|
|
||||||
sections = SettingsSectionMap()
|
|
||||||
loadCitraSettings(view)
|
|
||||||
if (!TextUtils.isEmpty(gameId)) {
|
|
||||||
loadCustomGameSettings(gameId!!, view)
|
|
||||||
}
|
|
||||||
isLoaded = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadCitraSettings(view: SettingsActivityView?) {
|
fun getAllOverrides(): Map<String, Any> = perGameOverrides.toMap()
|
||||||
for ((fileName) in configFileSectionsMap) {
|
|
||||||
sections.putAll(SettingsFile.readFile(fileName, view))
|
fun getAllGlobal(): Map<String, Any> = globalValues.toMap()
|
||||||
}
|
|
||||||
|
fun clearAll() {
|
||||||
|
globalValues.clear()
|
||||||
|
perGameOverrides.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
|
fun clearOverrides() {
|
||||||
// Custom game settings
|
perGameOverrides.clear()
|
||||||
mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
|
fun removePerGameSettings() {
|
||||||
for ((key, updatedSection) in updatedSections) {
|
clearOverrides()
|
||||||
if (sections.containsKey(key)) {
|
gameId = null
|
||||||
val originalSection = sections[key]
|
|
||||||
originalSection!!.mergeSection(updatedSection!!)
|
|
||||||
} else {
|
|
||||||
sections[key] = updatedSection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
companion object {
|
||||||
|
|
||||||
const val SECTION_CORE = "Core"
|
const val SECTION_CORE = "Core"
|
||||||
const val SECTION_SYSTEM = "System"
|
const val SECTION_SYSTEM = "System"
|
||||||
const val SECTION_CAMERA = "Camera"
|
const val SECTION_CAMERA = "Camera"
|
||||||
@ -182,7 +175,7 @@ class Settings {
|
|||||||
KEY_BUTTON_RIGHT
|
KEY_BUTTON_RIGHT
|
||||||
)
|
)
|
||||||
val axisTitles = listOf(
|
val axisTitles = listOf(
|
||||||
R.string.controller_axis_vertical,
|
R.string.controller_axis_vertical,
|
||||||
R.string.controller_axis_horizontal
|
R.string.controller_axis_horizontal
|
||||||
)
|
)
|
||||||
val dPadTitles = listOf(
|
val dPadTitles = listOf(
|
||||||
@ -234,20 +227,7 @@ class Settings {
|
|||||||
|
|
||||||
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
||||||
|
|
||||||
init {
|
/** Stores the settings as a singleton available everywhere.*/
|
||||||
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
|
val settings = Settings()
|
||||||
listOf(
|
|
||||||
SECTION_CORE,
|
|
||||||
SECTION_SYSTEM,
|
|
||||||
SECTION_CAMERA,
|
|
||||||
SECTION_CONTROLS,
|
|
||||||
SECTION_RENDERER,
|
|
||||||
SECTION_LAYOUT,
|
|
||||||
SECTION_STORAGE,
|
|
||||||
SECTION_UTILITY,
|
|
||||||
SECTION_AUDIO,
|
|
||||||
SECTION_DEBUG
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -7,5 +7,7 @@ package org.citra.citra_emu.features.settings.model
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
class SettingsViewModel : ViewModel() {
|
class SettingsViewModel : ViewModel() {
|
||||||
|
// the settings activity primarily manipulates its own copy of the settings object
|
||||||
|
// syncing it with the active settings only when saving
|
||||||
val settings = Settings()
|
val settings = Settings()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ enum class StringSetting(
|
|||||||
override val key: String,
|
override val key: String,
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: String
|
override val defaultValue: String
|
||||||
) : AbstractStringSetting {
|
) : AbstractSetting<String>{
|
||||||
INIT_TIME(SettingKeys.init_time(), Settings.SECTION_SYSTEM, "946731601"),
|
INIT_TIME(SettingKeys.init_time(), Settings.SECTION_SYSTEM, "946731601"),
|
||||||
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
|
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||||
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
|
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_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||||
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
|
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
|
||||||
|
|
||||||
override var string: String = defaultValue
|
override fun valueFromString(string: String) = string
|
||||||
|
|
||||||
override val valueAsString: String
|
|
||||||
get() = string
|
|
||||||
|
|
||||||
override val isRuntimeEditable: Boolean
|
override val isRuntimeEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
@ -47,6 +44,5 @@ enum class StringSetting(
|
|||||||
|
|
||||||
fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model.view
|
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.AbstractSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
|
|
||||||
class DateTimeSetting(
|
class DateTimeSetting(
|
||||||
setting: AbstractSetting?,
|
private val settings: Settings,
|
||||||
|
setting: AbstractSetting<String>?,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
private val defaultValue: 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) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_DATETIME_SETTING
|
override val type = TYPE_DATETIME_SETTING
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
val value: String
|
val value: String
|
||||||
get() = if (setting != null) {
|
get() = getValue?.invoke()
|
||||||
val setting = setting as AbstractStringSetting
|
?: if (setting != null) {
|
||||||
setting.string
|
settings.get(setting as AbstractSetting<String>)
|
||||||
} else {
|
} else {
|
||||||
defaultValue!!
|
defaultValue!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedValue(datetime: String): AbstractStringSetting {
|
@Suppress("UNCHECKED_CAST")
|
||||||
val stringSetting = setting as AbstractStringSetting
|
fun setSelectedValue(datetime: String) {
|
||||||
stringSetting.string = datetime
|
if (setValue != null) {
|
||||||
return stringSetting
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// 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.R
|
||||||
import org.citra.citra_emu.features.hotkeys.Hotkey
|
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.AbstractSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
|
||||||
import org.citra.citra_emu.features.settings.model.Settings
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
|
|
||||||
class InputBindingSetting(
|
class InputBindingSetting(
|
||||||
val abstractSetting: AbstractSetting,
|
val abstractSetting: AbstractSetting<*>,
|
||||||
titleId: Int
|
titleId: Int
|
||||||
) : SettingsItem(abstractSetting, titleId, 0) {
|
) : SettingsItem(abstractSetting, titleId, 0) {
|
||||||
private val context: Context get() = CitraApplication.appContext
|
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.
|
* Removes the old mapping for this key from the settings, e.g. on user clearing the setting.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun removeOldMapping() {
|
fun removeOldMapping() {
|
||||||
// Try remove all possible keys we wrote for this setting
|
// Try remove all possible keys we wrote for this setting
|
||||||
val oldKey = preferences.getString(reverseKey, "")
|
val oldKey = preferences.getString(reverseKey, "")
|
||||||
if (oldKey != "") {
|
if (oldKey != "") {
|
||||||
(setting as AbstractStringSetting).string = ""
|
//settings.set(setting as AbstractSetting<String>,"")
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.remove(abstractSetting.key) // Used for ui text
|
.remove(abstractSetting.key) // Used for ui text
|
||||||
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
|
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
|
||||||
|
|||||||
@ -5,26 +5,34 @@
|
|||||||
package org.citra.citra_emu.features.settings.model.view
|
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.AbstractSetting
|
||||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||||
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
|
|
||||||
class MultiChoiceSetting(
|
class MultiChoiceSetting(
|
||||||
setting: AbstractSetting?,
|
val settings: Settings,
|
||||||
|
setting: AbstractSetting<List<Int>>?,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val choicesId: Int,
|
val choicesId: Int,
|
||||||
val valuesId: Int,
|
val valuesId: Int,
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
val defaultValue: List<Int>? = 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) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_MULTI_CHOICE
|
override val type = TYPE_MULTI_CHOICE
|
||||||
|
|
||||||
val selectedValues: List<Int>
|
val selectedValues: List<Int>
|
||||||
get() {
|
get() {
|
||||||
|
if (getValue != null) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return getValue.invoke()
|
||||||
|
}
|
||||||
if (setting == null) {
|
if (setting == null) {
|
||||||
return defaultValue!!
|
return defaultValue!!
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val setting = setting as IntListSetting
|
return settings.get(setting as IntListSetting)
|
||||||
return setting.list
|
|
||||||
}catch (_: ClassCastException) {
|
}catch (_: ClassCastException) {
|
||||||
}
|
}
|
||||||
return defaultValue!!
|
return defaultValue!!
|
||||||
@ -35,12 +43,14 @@ class MultiChoiceSetting(
|
|||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||||
*
|
*
|
||||||
* @param selection New value of the int.
|
* @param selection New value of the int.
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
*/
|
||||||
fun setSelectedValue(selection: List<Int>): IntListSetting {
|
fun setSelectedValue(selection: List<Int>) {
|
||||||
val intSetting = setting as IntListSetting
|
if (setValue != null) {
|
||||||
intSetting.list = selection
|
setValue(selection)
|
||||||
return intSetting
|
}else {
|
||||||
|
val intSetting = setting as IntListSetting
|
||||||
|
settings.set(intSetting, selection)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import org.citra.citra_emu.features.settings.model.AbstractSetting
|
|||||||
* file.)
|
* file.)
|
||||||
*/
|
*/
|
||||||
abstract class SettingsItem(
|
abstract class SettingsItem(
|
||||||
var setting: AbstractSetting?,
|
var setting: AbstractSetting<*>?,
|
||||||
val nameId: Int,
|
val nameId: Int,
|
||||||
val descriptionId: Int
|
val descriptionId: Int
|
||||||
) {
|
) {
|
||||||
@ -35,6 +35,8 @@ abstract class SettingsItem(
|
|||||||
return this.isEditable && this.isEnabled
|
return this.isEditable && this.isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE_HEADER = 0
|
const val TYPE_HEADER = 0
|
||||||
const val TYPE_SWITCH = 1
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model.view
|
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.AbstractSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
|
|
||||||
class SingleChoiceSetting(
|
class SingleChoiceSetting(
|
||||||
setting: AbstractSetting?,
|
val settings: Settings,
|
||||||
|
setting: AbstractSetting<*>?,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val choicesId: Int,
|
val choicesId: Int,
|
||||||
val valuesId: Int,
|
val valuesId: Int,
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
val defaultValue: Int? = 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) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_SINGLE_CHOICE
|
override val type = TYPE_SINGLE_CHOICE
|
||||||
|
|
||||||
val selectedValue: Int
|
val selectedValue: Int
|
||||||
get() {
|
get() {
|
||||||
if (setting == null) {
|
if (getValue != null) {
|
||||||
return defaultValue!!
|
return getValue.invoke()
|
||||||
}
|
}
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
try {
|
val s = (setting as? AbstractSetting<Int>) ?: return defaultValue!!
|
||||||
val setting = setting as AbstractIntSetting
|
return settings.get(s)
|
||||||
return setting.int
|
|
||||||
} catch (_: ClassCastException) {
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val setting = setting as AbstractShortSetting
|
|
||||||
return setting.short.toInt()
|
|
||||||
} catch (_: ClassCastException) {
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a value to the backing int. If that int was previously null,
|
* Write a value to the backing int .
|
||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
|
||||||
*
|
|
||||||
* @param selection New value of the int.
|
* @param selection New value of the int.
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
*/
|
||||||
fun setSelectedValue(selection: Int): AbstractIntSetting {
|
fun setSelectedValue(selection: Int) {
|
||||||
val intSetting = setting as AbstractIntSetting
|
if (setValue != null) {
|
||||||
intSetting.int = selection
|
setValue(selection)
|
||||||
return intSetting
|
}else {
|
||||||
}
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val backSetting = setting as AbstractSetting<Int>
|
||||||
fun setSelectedValue(selection: Short): AbstractShortSetting {
|
settings.set(backSetting, selection)
|
||||||
val shortSetting = setting as AbstractShortSetting
|
}
|
||||||
shortSetting.short = selection
|
|
||||||
return shortSetting
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,15 +4,16 @@
|
|||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model.view
|
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.AbstractSetting
|
||||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
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 org.citra.citra_emu.utils.Log
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class SliderSetting(
|
class SliderSetting(
|
||||||
setting: AbstractSetting?,
|
val settings: Settings,
|
||||||
|
setting: AbstractSetting<*>?,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val min: Int,
|
val min: Int,
|
||||||
@ -20,17 +21,28 @@ class SliderSetting(
|
|||||||
val units: String,
|
val units: String,
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
val defaultValue: Float? = 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) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_SLIDER
|
override val type = TYPE_SLIDER
|
||||||
val selectedFloat: Float
|
val selectedFloat: Float
|
||||||
get() {
|
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 -> {
|
else -> {
|
||||||
Log.error("[SliderSetting] Error casting setting type.")
|
Log.error("[SliderSetting] Error casting setting type.")
|
||||||
-1f
|
-1f
|
||||||
@ -38,16 +50,32 @@ class SliderSetting(
|
|||||||
}
|
}
|
||||||
return ret.coerceIn(min.toFloat(), max.toFloat())
|
return ret.coerceIn(min.toFloat(), max.toFloat())
|
||||||
}
|
}
|
||||||
/**
|
fun roundedFloat(value: Float): Float {
|
||||||
* Write a value to the backing int. If that int was previously null,
|
val factor = 10f.pow(rounding)
|
||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
return (value * factor).roundToInt() / factor
|
||||||
*
|
}
|
||||||
* @param selection New value of the int.
|
|
||||||
* @return the existing setting with the new value applied.
|
val valueAsString: String
|
||||||
*/
|
get() = setting?.let {
|
||||||
fun setSelectedValue(selection: Int): AbstractIntSetting {
|
when (it.defaultValue) {
|
||||||
val intSetting = setting as AbstractIntSetting
|
is Int -> {
|
||||||
intSetting.int = selection
|
@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
|
return intSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,15 +84,14 @@ class SliderSetting(
|
|||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||||
*
|
*
|
||||||
* @param selection New value of the float.
|
* @param selection New value of the float.
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
*/
|
||||||
fun setSelectedValue(selection: Float): AbstractFloatSetting {
|
fun setSelectedValue(selection: Float) {
|
||||||
val floatSetting = setting as AbstractFloatSetting
|
if (setValue != null) {
|
||||||
if (floatSetting is ScaledFloatSetting) {
|
setValue(selection)
|
||||||
floatSetting.float = selection
|
}else {
|
||||||
} else {
|
@Suppress("UNCHECKED_CAST")
|
||||||
floatSetting.float = selection
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model.view
|
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.AbstractSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
|
|
||||||
class StringInputSetting(
|
class StringInputSetting(
|
||||||
setting: AbstractSetting?,
|
val settings: Settings,
|
||||||
|
setting: AbstractSetting<String>?,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val defaultValue: String,
|
val defaultValue: String,
|
||||||
val characterLimit: Int = 0,
|
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) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_STRING_INPUT
|
override val type = TYPE_STRING_INPUT
|
||||||
|
|
||||||
val selectedValue: String
|
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 {
|
fun setSelectedValue(selection: String) {
|
||||||
val stringSetting = setting as AbstractStringSetting
|
if (setValue != null) {
|
||||||
stringSetting.string = selection
|
setValue.invoke(selection)
|
||||||
return stringSetting
|
}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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model.view
|
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.AbstractSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
|
||||||
|
|
||||||
class StringSingleChoiceSetting(
|
class StringSingleChoiceSetting(
|
||||||
setting: AbstractSetting?,
|
val settings: Settings,
|
||||||
|
setting: AbstractSetting<String>?,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val choices: Array<String>,
|
val choices: Array<String>,
|
||||||
val values: Array<String>?,
|
val values: Array<String>?,
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
private val defaultValue: 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) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_STRING_SINGLE_CHOICE
|
override val type = TYPE_STRING_SINGLE_CHOICE
|
||||||
|
|
||||||
@ -31,22 +33,12 @@ class StringSingleChoiceSetting(
|
|||||||
|
|
||||||
val selectedValue: String
|
val selectedValue: String
|
||||||
get() {
|
get() {
|
||||||
|
if (getValue != null) return getValue.invoke()
|
||||||
if (setting == null) {
|
if (setting == null) {
|
||||||
return defaultValue!!
|
return defaultValue!!
|
||||||
}
|
}
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
try {
|
return settings.get(setting as AbstractSetting<String>)
|
||||||
val setting = setting as AbstractStringSetting
|
|
||||||
return setting.string
|
|
||||||
} catch (_: ClassCastException) {
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val setting = setting as AbstractShortSetting
|
|
||||||
return setting.short.toString()
|
|
||||||
} catch (_: ClassCastException) {
|
|
||||||
}
|
|
||||||
return defaultValue!!
|
|
||||||
}
|
}
|
||||||
val selectValueIndex: Int
|
val selectValueIndex: Int
|
||||||
get() {
|
get() {
|
||||||
@ -64,17 +56,14 @@ class StringSingleChoiceSetting(
|
|||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||||
*
|
*
|
||||||
* @param selection New value of the int.
|
* @param selection New value of the int.
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
*/
|
||||||
fun setSelectedValue(selection: String): AbstractStringSetting {
|
fun setSelectedValue(selection: String) {
|
||||||
val stringSetting = setting as AbstractStringSetting
|
if (setValue != null) {
|
||||||
stringSetting.string = selection
|
setValue(selection)
|
||||||
return stringSetting
|
}else {
|
||||||
}
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val stringSetting = setting as AbstractSetting<String>
|
||||||
fun setSelectedValue(selection: Short): AbstractShortSetting {
|
settings.set(stringSetting, selection)
|
||||||
val shortSetting = setting as AbstractShortSetting
|
}
|
||||||
shortSetting.short = selection
|
|
||||||
return shortSetting
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,38 +4,45 @@
|
|||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model.view
|
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.AbstractSetting
|
||||||
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
|
|
||||||
class SwitchSetting(
|
class SwitchSetting(
|
||||||
setting: AbstractBooleanSetting,
|
val settings: Settings,
|
||||||
|
setting: AbstractSetting<Boolean>?,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val key: String? = null,
|
val key: String? = null,
|
||||||
val defaultValue: Boolean = false,
|
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) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_SWITCH
|
override val type = TYPE_SWITCH
|
||||||
|
|
||||||
val isChecked: Boolean
|
val isChecked: Boolean
|
||||||
get() {
|
get() {
|
||||||
|
if (getValue != null) return getValue.invoke()
|
||||||
if (setting == null) {
|
if (setting == null) {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
val setting = setting as AbstractBooleanSetting
|
@Suppress("UNCHECKED_CAST")
|
||||||
return setting.boolean
|
val setting = setting as AbstractSetting<Boolean>
|
||||||
|
return settings.get(setting)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a value to the backing boolean.
|
* Write a value to the backing boolean.
|
||||||
*
|
*
|
||||||
* @param checked Pretty self explanatory.
|
* @param checked Pretty self explanatory.
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
*/
|
||||||
fun setChecked(checked: Boolean): AbstractBooleanSetting {
|
fun setChecked(checked: Boolean) {
|
||||||
val setting = setting as AbstractBooleanSetting
|
if (setValue != null) {
|
||||||
setting.boolean = checked
|
setValue(checked)
|
||||||
return setting
|
}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.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
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.R
|
||||||
import org.citra.citra_emu.databinding.ActivitySettingsBinding
|
import org.citra.citra_emu.databinding.ActivitySettingsBinding
|
||||||
import java.io.IOException
|
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.Settings
|
||||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
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.features.settings.utils.SettingsFile
|
||||||
import org.citra.citra_emu.utils.SystemSaveGame
|
import org.citra.citra_emu.utils.SystemSaveGame
|
||||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
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
|
import org.citra.citra_emu.utils.ThemeUtil
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||||
private val presenter = SettingsActivityPresenter(this)
|
|
||||||
|
|
||||||
private lateinit var binding: ActivitySettingsBinding
|
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 val settings: Settings get() = settingsViewModel.settings
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -63,9 +58,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
val launcher = intent
|
val launcher = intent
|
||||||
val gameID = launcher.getStringExtra(ARG_GAME_ID)
|
val gameID = launcher.getStringExtra(ARG_GAME_ID) ?: ""
|
||||||
val menuTag = launcher.getStringExtra(ARG_MENU_TAG)
|
val menuTag = launcher.getStringExtra(ARG_MENU_TAG) ?: ""
|
||||||
presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
|
presenter.onCreate(savedInstanceState, menuTag, gameID)
|
||||||
|
|
||||||
// Show "Back" button in the action bar for navigation
|
// Show "Back" button in the action bar for navigation
|
||||||
setSupportActionBar(binding.toolbarSettings)
|
setSupportActionBar(binding.toolbarSettings)
|
||||||
@ -211,13 +206,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||||||
controllerKeys.forEach { editor.remove(it) }
|
controllerKeys.forEach { editor.remove(it) }
|
||||||
editor.apply()
|
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
|
// 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)
|
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
|
||||||
if (!settingsFile.delete()) {
|
if (!settingsFile.delete()) {
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import org.citra.citra_emu.CitraApplication
|
|||||||
import org.citra.citra_emu.NativeLibrary
|
import org.citra.citra_emu.NativeLibrary
|
||||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
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.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.SystemSaveGame
|
||||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||||
import org.citra.citra_emu.utils.FileUtil
|
import org.citra.citra_emu.utils.FileUtil
|
||||||
@ -19,16 +21,32 @@ import org.citra.citra_emu.utils.Log
|
|||||||
import org.citra.citra_emu.utils.PermissionsHandler
|
import org.citra.citra_emu.utils.PermissionsHandler
|
||||||
import org.citra.citra_emu.utils.TurboHelper
|
import org.citra.citra_emu.utils.TurboHelper
|
||||||
|
|
||||||
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
|
class SettingsActivityPresenter(private val activityView: SettingsActivityView, private val viewModel: SettingsViewModel) {
|
||||||
val settings: Settings get() = activityView.settings
|
val settings: Settings get() = viewModel.settings
|
||||||
|
|
||||||
private var shouldSave = false
|
private var shouldSave = false
|
||||||
private lateinit var menuTag: String
|
private lateinit var menuTag: String
|
||||||
private lateinit var gameId: String
|
private lateinit var gameId: String
|
||||||
|
private var perGameInGlobalContext = false
|
||||||
|
|
||||||
fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
|
fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
|
||||||
this.menuTag = menuTag
|
this.menuTag = menuTag
|
||||||
this.gameId = gameId
|
this.gameId = gameId
|
||||||
|
|
||||||
|
perGameInGlobalContext = gameId != "" && !Settings.settings.isPerGame()
|
||||||
|
|
||||||
|
// sync the active settings into my local settings appropriately
|
||||||
|
// if we are editing global settings rom a game, this should just work
|
||||||
|
// to sync only the global settings into the local version
|
||||||
|
settings.gameId = gameId
|
||||||
|
settings.mergeSettings(Settings.settings)
|
||||||
|
|
||||||
|
// if we are editing per-game settings when the game is not loaded,
|
||||||
|
// we need to load the per-game settings now from the ini file
|
||||||
|
if (perGameInGlobalContext) {
|
||||||
|
SettingsFile.loadSettings(settings, gameId)
|
||||||
|
}
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
|
shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
|
||||||
}
|
}
|
||||||
@ -47,13 +65,6 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun loadSettingsUI() {
|
private fun loadSettingsUI() {
|
||||||
if (!settings.isLoaded) {
|
|
||||||
if (!TextUtils.isEmpty(gameId)) {
|
|
||||||
settings.loadSettings(gameId, activityView)
|
|
||||||
} else {
|
|
||||||
settings.loadSettings(activityView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
activityView.showSettingsFragment(menuTag, false, gameId)
|
activityView.showSettingsFragment(menuTag, false, gameId)
|
||||||
activityView.onSettingsFileLoaded()
|
activityView.onSettingsFileLoaded()
|
||||||
}
|
}
|
||||||
@ -72,7 +83,8 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
|||||||
val nomediaFileExists: Boolean
|
val nomediaFileExists: Boolean
|
||||||
try {
|
try {
|
||||||
dataDirTreeUri = PermissionsHandler.citraDirectory
|
dataDirTreeUri = PermissionsHandler.citraDirectory
|
||||||
dataDirDocument = DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
|
dataDirDocument =
|
||||||
|
DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
|
||||||
nomediaFileDocument = dataDirDocument.findFile(".nomedia")
|
nomediaFileDocument = dataDirDocument.findFile(".nomedia")
|
||||||
nomediaFileExists = (nomediaFileDocument != null)
|
nomediaFileExists = (nomediaFileDocument != null)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -80,7 +92,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BooleanSetting.ANDROID_HIDE_IMAGES.boolean) {
|
if (settings.get(BooleanSetting.ANDROID_HIDE_IMAGES)) {
|
||||||
if (!nomediaFileExists) {
|
if (!nomediaFileExists) {
|
||||||
Log.info("[SettingsActivity]: Attempting to create .nomedia in user data directory")
|
Log.info("[SettingsActivity]: Attempting to create .nomedia in user data directory")
|
||||||
FileUtil.createFile(dataDirTreeUri.toString(), ".nomedia")
|
FileUtil.createFile(dataDirTreeUri.toString(), ".nomedia")
|
||||||
@ -94,14 +106,18 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
|||||||
fun onStop(finishing: Boolean) {
|
fun onStop(finishing: Boolean) {
|
||||||
if (finishing && shouldSave) {
|
if (finishing && shouldSave) {
|
||||||
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||||
settings.saveSettings(activityView)
|
if (settings.isPerGame()) {
|
||||||
//added to ensure that layout changes take effect as soon as settings window closes
|
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.reloadSettings()
|
||||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||||
updateAndroidImageVisibility()
|
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() {
|
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.ListItemSettingBinding
|
||||||
import org.citra.citra_emu.databinding.ListItemSettingSwitchBinding
|
import org.citra.citra_emu.databinding.ListItemSettingSwitchBinding
|
||||||
import org.citra.citra_emu.databinding.ListItemSettingsHeaderBinding
|
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.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.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.DateTimeSetting
|
||||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||||
@ -74,11 +67,12 @@ import java.text.SimpleDateFormat
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class SettingsAdapter(
|
class SettingsAdapter(
|
||||||
private val fragmentView: SettingsFragmentView,
|
val fragmentView: SettingsFragmentView,
|
||||||
public val context: Context
|
val context: Context
|
||||||
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
|
) : RecyclerView.Adapter<SettingViewHolder<SettingsItem>?>(), DialogInterface.OnClickListener,
|
||||||
DialogInterface.OnMultiChoiceClickListener {
|
DialogInterface.OnMultiChoiceClickListener {
|
||||||
private var settings: ArrayList<SettingsItem>? = null
|
private var settings: ArrayList<SettingsItem>? = null
|
||||||
|
var isPerGame: Boolean = false
|
||||||
private var clickedItem: SettingsItem? = null
|
private var clickedItem: SettingsItem? = null
|
||||||
private var clickedPosition: Int
|
private var clickedPosition: Int
|
||||||
private var dialog: AlertDialog? = null
|
private var dialog: AlertDialog? = null
|
||||||
@ -94,7 +88,7 @@ class SettingsAdapter(
|
|||||||
clickedPosition = -1
|
clickedPosition = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder<*> {
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
SettingsItem.TYPE_HEADER -> {
|
SettingsItem.TYPE_HEADER -> {
|
||||||
@ -144,7 +138,7 @@ class SettingsAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SettingViewHolder<SettingsItem>, position: Int) {
|
||||||
getItem(position)?.let { holder.bind(it) }
|
getItem(position)?.let { holder.bind(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,14 +220,15 @@ class SettingsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
|
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
|
||||||
val setting = item.setChecked(checked)
|
item.setChecked(checked)
|
||||||
fragmentView.putSetting(setting)
|
|
||||||
fragmentView.onSettingChanged()
|
fragmentView.onSettingChanged()
|
||||||
|
|
||||||
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
|
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
|
||||||
if (fragmentView.activityView != null)
|
if (fragmentView.activityView != null)
|
||||||
// Reload the settings list to update the UI
|
// Reload the settings list to update the UI
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
|
|
||||||
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSingleChoiceClick(item: SingleChoiceSetting) {
|
private fun onSingleChoiceClick(item: SingleChoiceSetting) {
|
||||||
@ -338,8 +333,7 @@ class SettingsAdapter(
|
|||||||
fragmentView.onSettingChanged()
|
fragmentView.onSettingChanged()
|
||||||
}
|
}
|
||||||
notifyItemChanged(clickedPosition)
|
notifyItemChanged(clickedPosition)
|
||||||
val setting = item.setSelectedValue(rtcString)
|
item.setSelectedValue(rtcString)
|
||||||
fragmentView.putSetting(setting)
|
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
clickedItem = null
|
clickedItem = null
|
||||||
}
|
}
|
||||||
@ -359,7 +353,7 @@ class SettingsAdapter(
|
|||||||
val sliderBinding = DialogSliderBinding.inflate(inflater)
|
val sliderBinding = DialogSliderBinding.inflate(inflater)
|
||||||
textInputLayout = sliderBinding.textInput
|
textInputLayout = sliderBinding.textInput
|
||||||
textSliderValue = sliderBinding.textValue
|
textSliderValue = sliderBinding.textValue
|
||||||
if (item.setting is FloatSetting) {
|
if (item.setting?.defaultValue is Float) {
|
||||||
textSliderValue?.let {
|
textSliderValue?.let {
|
||||||
it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
|
it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
|
||||||
it.setText(sliderProgress.toString())
|
it.setText(sliderProgress.toString())
|
||||||
@ -393,9 +387,9 @@ class SettingsAdapter(
|
|||||||
})
|
})
|
||||||
|
|
||||||
addOnChangeListener { _: Slider, value: Float, _: Boolean ->
|
addOnChangeListener { _: Slider, value: Float, _: Boolean ->
|
||||||
sliderProgress = (value * 100).roundToInt().toFloat() / 100f
|
sliderProgress = item.roundedFloat(value)
|
||||||
var sliderString = sliderProgress.toString()
|
var sliderString = sliderProgress.toString()
|
||||||
if (item.setting !is FloatSetting) {
|
if (item.setting?.defaultValue !is Float) {
|
||||||
sliderString = sliderProgress.roundToInt().toString()
|
sliderString = sliderProgress.roundToInt().toString()
|
||||||
if (textSliderValue?.text.toString() != sliderString) {
|
if (textSliderValue?.text.toString() != sliderString) {
|
||||||
textSliderValue?.setText(sliderString)
|
textSliderValue?.setText(sliderString)
|
||||||
@ -418,16 +412,12 @@ class SettingsAdapter(
|
|||||||
.setPositiveButton(android.R.string.ok, this)
|
.setPositiveButton(android.R.string.ok, this)
|
||||||
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
||||||
.setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int ->
|
.setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int ->
|
||||||
sliderBinding.slider?.value = when (item.setting) {
|
sliderBinding.slider.value = when (item.setting?.defaultValue) {
|
||||||
is ScaledFloatSetting -> {
|
is Float -> item.setting!!.defaultValue as Float
|
||||||
val scaledSetting = item.setting as ScaledFloatSetting
|
is Int -> (item.setting!!.defaultValue as Int).toFloat()
|
||||||
scaledSetting.defaultValue * scaledSetting.scale
|
else -> 0f
|
||||||
}
|
|
||||||
|
|
||||||
is FloatSetting -> (item.setting as FloatSetting).defaultValue
|
|
||||||
else -> item.defaultValue ?: 0f
|
|
||||||
}
|
}
|
||||||
onClick(dialog, which)
|
onClick(dialog, which)
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
@ -478,26 +468,9 @@ class SettingsAdapter(
|
|||||||
is SingleChoiceSetting -> {
|
is SingleChoiceSetting -> {
|
||||||
val scSetting = clickedItem as? SingleChoiceSetting
|
val scSetting = clickedItem as? SingleChoiceSetting
|
||||||
scSetting?.let {
|
scSetting?.let {
|
||||||
val setting = when (it.setting) {
|
val value = getValueForSingleChoiceSelection(it, which)
|
||||||
is AbstractIntSetting -> {
|
if (it.selectedValue != value) fragmentView?.onSettingChanged()
|
||||||
val value = getValueForSingleChoiceSelection(it, which)
|
it.setSelectedValue(value)
|
||||||
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)
|
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
closeDialog()
|
closeDialog()
|
||||||
}
|
}
|
||||||
@ -506,22 +479,9 @@ class SettingsAdapter(
|
|||||||
is StringSingleChoiceSetting -> {
|
is StringSingleChoiceSetting -> {
|
||||||
val scSetting = clickedItem as? StringSingleChoiceSetting
|
val scSetting = clickedItem as? StringSingleChoiceSetting
|
||||||
scSetting?.let {
|
scSetting?.let {
|
||||||
val setting = when (it.setting) {
|
val value = it.getValueAt(which) ?: ""
|
||||||
is AbstractStringSetting -> {
|
if (it.selectedValue != value) fragmentView?.onSettingChanged()
|
||||||
val value = it.getValueAt(which)
|
it.setSelectedValue(value)
|
||||||
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)
|
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
closeDialog()
|
closeDialog()
|
||||||
}
|
}
|
||||||
@ -530,21 +490,12 @@ class SettingsAdapter(
|
|||||||
is SliderSetting -> {
|
is SliderSetting -> {
|
||||||
val sliderSetting = clickedItem as? SliderSetting
|
val sliderSetting = clickedItem as? SliderSetting
|
||||||
sliderSetting?.let {
|
sliderSetting?.let {
|
||||||
val sliderval = (it.selectedFloat * 100).roundToInt().toFloat() / 100
|
val sliderval = it.roundedFloat(sliderProgress)
|
||||||
if (sliderval != sliderProgress) {
|
if (sliderval != it.selectedFloat) fragmentView.onSettingChanged()
|
||||||
fragmentView?.onSettingChanged()
|
val s = it.setting
|
||||||
}
|
when {
|
||||||
when (it.setting) {
|
it.setting?.defaultValue is Int -> it.setSelectedValue(sliderProgress.roundToInt())
|
||||||
is AbstractIntSetting -> {
|
else -> it.setSelectedValue(sliderProgress)
|
||||||
val value = sliderProgress.roundToInt()
|
|
||||||
val setting = it.setSelectedValue(value)
|
|
||||||
fragmentView?.putSetting(setting)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
val setting = it.setSelectedValue(sliderProgress)
|
|
||||||
fragmentView?.putSetting(setting)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
closeDialog()
|
closeDialog()
|
||||||
@ -557,8 +508,7 @@ class SettingsAdapter(
|
|||||||
if (it.selectedValue != textInputValue) {
|
if (it.selectedValue != textInputValue) {
|
||||||
fragmentView?.onSettingChanged()
|
fragmentView?.onSettingChanged()
|
||||||
}
|
}
|
||||||
val setting = it.setSelectedValue(textInputValue ?: "")
|
it.setSelectedValue(textInputValue)
|
||||||
fragmentView?.putSetting(setting)
|
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
closeDialog()
|
closeDialog()
|
||||||
}
|
}
|
||||||
@ -575,36 +525,19 @@ class SettingsAdapter(
|
|||||||
mcsetting?.let {
|
mcsetting?.let {
|
||||||
val value = getValueForMultiChoiceSelection(it, which)
|
val value = getValueForMultiChoiceSelection(it, which)
|
||||||
if (it.selectedValues.contains(value) != isChecked) {
|
if (it.selectedValues.contains(value) != isChecked) {
|
||||||
val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
|
it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
|
||||||
fragmentView?.putSetting(setting)
|
fragmentView.onSettingChanged()
|
||||||
fragmentView?.onSettingChanged()
|
|
||||||
}
|
}
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
|
fun onLongClick(setting: AbstractSetting<*>, position: Int): Boolean {
|
||||||
MaterialAlertDialogBuilder(context)
|
MaterialAlertDialogBuilder(context)
|
||||||
.setMessage(R.string.reset_setting_confirmation)
|
.setMessage(R.string.reset_setting_confirmation)
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
when (setting) {
|
resetSettingToDefault(setting, position)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
@ -612,6 +545,23 @@ class SettingsAdapter(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> resetSettingToDefault(setting: AbstractSetting<T>, position: Int) {
|
||||||
|
val settings = fragmentView.activityView?.settings ?: return
|
||||||
|
settings.set(setting,setting.defaultValue)
|
||||||
|
notifyItemChanged(position)
|
||||||
|
fragmentView.onSettingChanged()
|
||||||
|
fragmentView.loadSettingsList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> resetSettingToGlobal(setting: AbstractSetting<T>, position: Int) {
|
||||||
|
val settings = fragmentView.activityView?.settings ?: return
|
||||||
|
settings.clearOverride(setting)
|
||||||
|
notifyItemChanged(position)
|
||||||
|
fragmentView.onSettingChanged()
|
||||||
|
fragmentView.loadSettingsList()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun onInputBindingLongClick(setting: InputBindingSetting, position: Int): Boolean {
|
fun onInputBindingLongClick(setting: InputBindingSetting, position: Int): Boolean {
|
||||||
MaterialAlertDialogBuilder(context)
|
MaterialAlertDialogBuilder(context)
|
||||||
.setMessage(R.string.reset_setting_confirmation)
|
.setMessage(R.string.reset_setting_confirmation)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -13,16 +13,19 @@ import androidx.core.view.ViewCompat
|
|||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
|
||||||
import org.citra.citra_emu.databinding.FragmentSettingsBinding
|
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 org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||||
|
import kotlin.getValue
|
||||||
|
|
||||||
class SettingsFragment : Fragment(), SettingsFragmentView {
|
class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||||
override var activityView: SettingsActivityView? = null
|
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 settingsAdapter: SettingsAdapter? = null
|
||||||
|
|
||||||
private var _binding: FragmentSettingsBinding? = null
|
private var _binding: FragmentSettingsBinding? = null
|
||||||
@ -37,7 +40,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
|
val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
|
||||||
val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
|
val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
|
||||||
fragmentPresenter.onCreate(menuTag!!, gameId!!)
|
fragmentPresenter.onCreate(menuTag!!, gameId!!, settingsViewModel.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@ -88,10 +91,6 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
|||||||
activityView!!.showToastMessage(message!!, is_long)
|
activityView!!.showToastMessage(message!!, is_long)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putSetting(setting: AbstractSetting) {
|
|
||||||
fragmentPresenter.putSetting(setting)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSettingChanged() {
|
override fun onSettingChanged() {
|
||||||
activityView!!.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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.ui
|
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
|
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,13 +44,6 @@ interface SettingsFragmentView {
|
|||||||
*/
|
*/
|
||||||
fun showToastMessage(message: String?, is_long: Boolean)
|
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.
|
* 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
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder<DateTimeSetting>(binding.root, adapter) {
|
||||||
private lateinit var setting: DateTimeSetting
|
override lateinit var setting: DateTimeSetting
|
||||||
|
|
||||||
@SuppressLint("SimpleDateFormat")
|
@SuppressLint("SimpleDateFormat")
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item as DateTimeSetting
|
setting = item as? DateTimeSetting ?: return
|
||||||
binding.textSettingName.setText(item.nameId)
|
binding.textSettingName.setText(item.nameId)
|
||||||
if (item.descriptionId != 0) {
|
if (item.descriptionId != 0) {
|
||||||
binding.textSettingDescription.visibility = View.VISIBLE
|
binding.textSettingDescription.visibility = View.VISIBLE
|
||||||
@ -56,6 +55,9 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||||||
binding.textSettingDescription.alpha = 0.5f
|
binding.textSettingDescription.alpha = 0.5f
|
||||||
binding.textSettingValue.alpha = 0.5f
|
binding.textSettingValue.alpha = 0.5f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showGlobalButtonIfNeeded(binding.buttonUseGlobal, position)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
override fun onClick(clicked: View) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// 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
|
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: SettingsAdapter) :
|
class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder<SettingsItem>(binding.root, adapter) {
|
||||||
|
override var setting = null
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener(null)
|
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
|
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
class InputBindingSettingViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class InputBindingSettingViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder<InputBindingSetting>(binding.root, adapter) {
|
||||||
private lateinit var setting: InputBindingSetting
|
override lateinit var setting: InputBindingSetting
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
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
|
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder<SettingsItem>(binding.root, adapter) {
|
||||||
private lateinit var setting: SettingsItem
|
override lateinit var setting: SettingsItem
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item
|
setting = item
|
||||||
binding.textSettingName.setText(item.nameId)
|
binding.textSettingName.setText(item.nameId)
|
||||||
@ -35,6 +34,8 @@ class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Settin
|
|||||||
binding.textSettingDescription.alpha = 0.5f
|
binding.textSettingDescription.alpha = 0.5f
|
||||||
binding.textSettingValue.alpha = 0.5f
|
binding.textSettingValue.alpha = 0.5f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showGlobalButtonIfNeeded(binding.buttonUseGlobal, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTextSetting(): String {
|
private fun getTextSetting(): String {
|
||||||
|
|||||||
@ -14,8 +14,8 @@ import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
|||||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder<RunnableSetting>(binding.root, adapter) {
|
||||||
private lateinit var setting: RunnableSetting
|
override lateinit var setting: RunnableSetting
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item as RunnableSetting
|
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.model.view.SettingsItem
|
||||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
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 {
|
RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener(this)
|
itemView.setOnClickListener(this)
|
||||||
itemView.setOnLongClickListener(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
|
* Called by the adapter to set this ViewHolder's child views to display the list item
|
||||||
* it must now represent.
|
* it must now represent.
|
||||||
@ -34,4 +37,22 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
|
|||||||
abstract override fun onClick(clicked: View)
|
abstract override fun onClick(clicked: View)
|
||||||
|
|
||||||
abstract override fun onLongClick(clicked: View): Boolean
|
abstract override fun onLongClick(clicked: View): Boolean
|
||||||
|
|
||||||
|
fun showGlobalButtonIfNeeded(buttonUseGlobal: View, position: Int) {
|
||||||
|
setting ?: return
|
||||||
|
// Show "Revert to global" button in Custom Settings if applicable.
|
||||||
|
val settings = adapter.fragmentView.activityView?.settings
|
||||||
|
val showGlobal = settings?.isPerGame() == true
|
||||||
|
&& setting?.setting != null
|
||||||
|
&& settings.hasOverride(setting!!.setting!!)
|
||||||
|
|
||||||
|
buttonUseGlobal.visibility = if (showGlobal) View.VISIBLE else View.GONE
|
||||||
|
if (showGlobal) {
|
||||||
|
buttonUseGlobal.setOnClickListener {
|
||||||
|
setting?.setting?.let { descriptor ->
|
||||||
|
adapter.resetSettingToGlobal(descriptor, bindingAdapterPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSettin
|
|||||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder<SettingsItem>(binding.root, adapter) {
|
||||||
private lateinit var setting: SettingsItem
|
override lateinit var setting: SettingsItem
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item
|
setting = item
|
||||||
@ -36,6 +36,8 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
|||||||
binding.textSettingDescription.alpha = 0.5f
|
binding.textSettingDescription.alpha = 0.5f
|
||||||
binding.textSettingValue.alpha = 0.5f
|
binding.textSettingValue.alpha = 0.5f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showGlobalButtonIfNeeded(binding.buttonUseGlobal, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTextSetting(): String {
|
private fun getTextSetting(): String {
|
||||||
|
|||||||
@ -6,17 +6,14 @@ package org.citra.citra_emu.features.settings.ui.viewholder
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import org.citra.citra_emu.databinding.ListItemSettingBinding
|
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.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.SettingsItem
|
||||||
import org.citra.citra_emu.features.settings.model.view.SliderSetting
|
import org.citra.citra_emu.features.settings.model.view.SliderSetting
|
||||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder<SliderSetting>(binding.root, adapter) {
|
||||||
private lateinit var setting: SliderSetting
|
override lateinit var setting: SliderSetting
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item as SliderSetting
|
setting = item as SliderSetting
|
||||||
@ -28,12 +25,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
|||||||
binding.textSettingDescription.visibility = View.GONE
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
}
|
}
|
||||||
binding.textSettingValue.visibility = View.VISIBLE
|
binding.textSettingValue.visibility = View.VISIBLE
|
||||||
binding.textSettingValue.text = when (setting.setting) {
|
binding.textSettingValue.text = "${setting.valueAsString}${setting.units}"
|
||||||
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}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setting.isActive) {
|
if (setting.isActive) {
|
||||||
binding.textSettingName.alpha = 1f
|
binding.textSettingName.alpha = 1f
|
||||||
@ -44,6 +36,8 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
|||||||
binding.textSettingDescription.alpha = 0.5f
|
binding.textSettingDescription.alpha = 0.5f
|
||||||
binding.textSettingValue.alpha = 0.5f
|
binding.textSettingValue.alpha = 0.5f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showGlobalButtonIfNeeded(binding.buttonUseGlobal, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
override fun onClick(clicked: View) {
|
||||||
|
|||||||
@ -11,11 +11,10 @@ import org.citra.citra_emu.features.settings.model.view.StringInputSetting
|
|||||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder<StringInputSetting>(binding.root, adapter) {
|
||||||
private lateinit var setting: SettingsItem
|
override lateinit var setting: StringInputSetting
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item
|
setting = item as StringInputSetting
|
||||||
binding.textSettingName.setText(item.nameId)
|
binding.textSettingName.setText(item.nameId)
|
||||||
if (item.descriptionId != 0) {
|
if (item.descriptionId != 0) {
|
||||||
binding.textSettingDescription.visibility = View.VISIBLE
|
binding.textSettingDescription.visibility = View.VISIBLE
|
||||||
@ -24,7 +23,7 @@ class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: Settin
|
|||||||
binding.textSettingDescription.visibility = View.GONE
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
}
|
}
|
||||||
binding.textSettingValue.visibility = View.VISIBLE
|
binding.textSettingValue.visibility = View.VISIBLE
|
||||||
binding.textSettingValue.text = setting.setting?.valueAsString
|
binding.textSettingValue.text = setting.selectedValue
|
||||||
|
|
||||||
if (setting.isActive) {
|
if (setting.isActive) {
|
||||||
binding.textSettingName.alpha = 1f
|
binding.textSettingName.alpha = 1f
|
||||||
@ -35,6 +34,8 @@ class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: Settin
|
|||||||
binding.textSettingDescription.alpha = 0.5f
|
binding.textSettingDescription.alpha = 0.5f
|
||||||
binding.textSettingValue.alpha = 0.5f
|
binding.textSettingValue.alpha = 0.5f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showGlobalButtonIfNeeded(binding.buttonUseGlobal, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
override fun onClick(clicked: View) {
|
||||||
@ -42,7 +43,7 @@ class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: Settin
|
|||||||
adapter.onClickDisabledSetting(!setting.isEditable)
|
adapter.onClickDisabledSetting(!setting.isEditable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
adapter.onStringInputClick((setting as StringInputSetting), bindingAdapterPosition)
|
adapter.onStringInputClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
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
|
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder<SubmenuSetting>(binding.root, adapter) {
|
||||||
private lateinit var item: SubmenuSetting
|
override lateinit var setting: SubmenuSetting
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
this.item = item as SubmenuSetting
|
setting = item as SubmenuSetting
|
||||||
if (item.iconId == 0) {
|
if (item.iconId == 0) {
|
||||||
binding.icon.visibility = View.GONE
|
binding.icon.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
@ -40,7 +40,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
override fun onClick(clicked: View) {
|
||||||
adapter.onSubmenuClick(item)
|
adapter.onSubmenuClick(setting)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
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
|
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: 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) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item as SwitchSetting
|
setting = item as SwitchSetting
|
||||||
@ -38,6 +38,8 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
|||||||
val textAlpha = if (setting.isActive) 1f else 0.5f
|
val textAlpha = if (setting.isActive) 1f else 0.5f
|
||||||
binding.textSettingName.alpha = textAlpha
|
binding.textSettingName.alpha = textAlpha
|
||||||
binding.textSettingDescription.alpha = textAlpha
|
binding.textSettingDescription.alpha = textAlpha
|
||||||
|
|
||||||
|
showGlobalButtonIfNeeded(binding.buttonUseGlobal, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
override fun onClick(clicked: View) {
|
||||||
|
|||||||
@ -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.FloatSetting
|
||||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
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.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.SettingSection
|
|
||||||
import org.citra.citra_emu.features.settings.model.Settings.SettingsSectionMap
|
|
||||||
import org.citra.citra_emu.features.settings.model.StringSetting
|
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||||
import org.citra.citra_emu.features.settings.ui.SettingsActivityView
|
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.DirectoryInitialization.userDirectory
|
||||||
import org.citra.citra_emu.utils.Log
|
import org.citra.citra_emu.utils.Log
|
||||||
import org.ini4j.Wini
|
import org.ini4j.Wini
|
||||||
@ -27,7 +24,6 @@ import java.io.BufferedReader
|
|||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.util.TreeMap
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,40 +32,46 @@ import java.util.TreeMap
|
|||||||
object SettingsFile {
|
object SettingsFile {
|
||||||
const val FILE_NAME_CONFIG = "config"
|
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
|
* Reads a given .ini file from disk and updates a instance of the Settings class appropriately
|
||||||
* effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
|
|
||||||
* failed.
|
|
||||||
*
|
*
|
||||||
* @param ini The ini file to load the settings from
|
* @param ini The ini file to load the settings from
|
||||||
|
* @param settings The Settings instance to edit
|
||||||
* @param isCustomGame
|
* @param isCustomGame
|
||||||
* @param view The current view.
|
* @param view The current view.
|
||||||
* @return An Observable that emits a HashMap of the file's contents, then completes.
|
* @return An Observable that emits a HashMap of the file's contents, then completes.
|
||||||
*/
|
*/
|
||||||
fun readFile(
|
fun readFile(
|
||||||
ini: DocumentFile,
|
ini: DocumentFile,
|
||||||
|
settings: Settings,
|
||||||
isCustomGame: Boolean,
|
isCustomGame: Boolean,
|
||||||
view: SettingsActivityView?
|
view: SettingsActivityView?
|
||||||
): HashMap<String, SettingSection?> {
|
) {
|
||||||
val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
|
|
||||||
var reader: BufferedReader? = null
|
var reader: BufferedReader? = null
|
||||||
try {
|
try {
|
||||||
val context: Context = CitraApplication.appContext
|
val context: Context = CitraApplication.appContext
|
||||||
val inputStream = context.contentResolver.openInputStream(ini.uri)
|
val inputStream = context.contentResolver.openInputStream(ini.uri)
|
||||||
reader = BufferedReader(InputStreamReader(inputStream))
|
reader = BufferedReader(InputStreamReader(inputStream))
|
||||||
var current: SettingSection? = null
|
var currentSection: String? = null
|
||||||
var line: String?
|
var line: String?
|
||||||
while (reader.readLine().also { line = it } != null) {
|
while (reader.readLine().also { line = it } != null) {
|
||||||
if (line!!.startsWith("[") && line!!.endsWith("]")) {
|
if (line!!.startsWith("[") && line.endsWith("]")) {
|
||||||
current = sectionFromLine(line!!, isCustomGame)
|
currentSection = line.substring(1, line.length-1)
|
||||||
sections[current.name] = current
|
} else if (currentSection != null) {
|
||||||
} else if (current != null) {
|
val pair = parseLineToKeyValuePair(line) ?: continue
|
||||||
val setting = settingFromLine(line!!)
|
val (key, rawValue) = pair
|
||||||
if (setting != null) {
|
val descriptor = findSettingByKey(key) ?: continue
|
||||||
current.putSetting(setting)
|
loadSettingInto(settings, descriptor, rawValue, isCustomGame)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: FileNotFoundException) {
|
} 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
|
* Load global settings from the config file into the settings instance
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
fun readCustomGameSettings(
|
fun loadSettings(settings: Settings, view: SettingsActivityView? = null) {
|
||||||
gameId: String,
|
readFile(getSettingsFile(FILE_NAME_CONFIG),settings,false,view)
|
||||||
view: SettingsActivityView?
|
|
||||||
): HashMap<String, SettingSection?> {
|
|
||||||
return readFile(getCustomGameSettingsFile(gameId), true, 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.
|
* telling why it failed.
|
||||||
*
|
*
|
||||||
* @param fileName The target filename without a path or extension.
|
* @param settings The Settings instance we are saving
|
||||||
* @param sections The HashMap containing the Settings we want to serialize.
|
|
||||||
* @param view The current view.
|
* @param view The current view.
|
||||||
*/
|
*/
|
||||||
fun saveFile(
|
fun saveGlobalFile(
|
||||||
fileName: String,
|
settings: Settings,
|
||||||
sections: TreeMap<String, SettingSection?>,
|
view: SettingsActivityView? = null
|
||||||
view: SettingsActivityView
|
|
||||||
) {
|
) {
|
||||||
val ini = getSettingsFile(fileName)
|
val ini = getSettingsFile(FILE_NAME_CONFIG)
|
||||||
try {
|
try {
|
||||||
val context: Context = CitraApplication.appContext
|
val context: Context = CitraApplication.appContext
|
||||||
val inputStream = context.contentResolver.openInputStream(ini.uri)
|
val inputStream = context.contentResolver.openInputStream(ini.uri)
|
||||||
val writer = Wini(inputStream)
|
val writer = Wini(inputStream)
|
||||||
val keySet: Set<String> = sections.keys
|
|
||||||
for (key in keySet) {
|
|
||||||
val section = sections[key]
|
|
||||||
writeSection(writer, section!!)
|
|
||||||
}
|
|
||||||
inputStream!!.close()
|
inputStream!!.close()
|
||||||
|
|
||||||
|
for (setting in allSettings) {
|
||||||
|
val value = settings.getGlobal(setting) ?: continue
|
||||||
|
writeSettingToWini(writer, setting, value)
|
||||||
|
}
|
||||||
|
|
||||||
val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt")
|
val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt")
|
||||||
writer.store(outputStream)
|
writer.store(outputStream)
|
||||||
outputStream!!.flush()
|
outputStream!!.flush()
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.error("[SettingsFile] File not found: $fileName.ini: ${e.message}")
|
Log.error("[SettingsFile] File not found: $FILE_NAME_CONFIG.ini: ${e.message}")
|
||||||
view.showToastMessage(
|
view?.showToastMessage(
|
||||||
CitraApplication.appContext
|
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,
|
* Save the per-game overrides to a per-game config file
|
||||||
setting: AbstractSetting
|
*/
|
||||||
|
|
||||||
|
fun saveCustomFile(
|
||||||
|
settings: Settings,
|
||||||
|
view: SettingsActivityView? = null
|
||||||
) {
|
) {
|
||||||
val ini = getSettingsFile(fileName)
|
if (!settings.isPerGame()) return
|
||||||
|
val ini = getOrCreateCustomGameSettingsFile(settings.gameId!!)
|
||||||
try {
|
try {
|
||||||
val context: Context = CitraApplication.appContext
|
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 inputStream = context.contentResolver.openInputStream(ini.uri)
|
||||||
val writer = Wini(inputStream)
|
val writer = if (inputStream != null) Wini(inputStream) else Wini()
|
||||||
writer.put(setting.section, setting.key, setting.valueAsString)
|
inputStream?.close()
|
||||||
inputStream!!.close()
|
writeSettingToWini(writer, setting, value as Any)
|
||||||
val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt")
|
val outputStream = context.contentResolver.openOutputStream(ini.uri, "wt")
|
||||||
writer.store(outputStream)
|
writer.store(outputStream)
|
||||||
outputStream!!.flush()
|
outputStream!!.flush()
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
} catch (e: Exception) {
|
} 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? {
|
private fun parseLineToKeyValuePair(line: String): Pair<String, String>? {
|
||||||
return if (sectionsMap.getForward(generalSectionName) != null) {
|
val splitLine = line.split("=".toRegex(), limit = 2)
|
||||||
sectionsMap.getForward(generalSectionName)
|
if (splitLine.size != 2) return null
|
||||||
} else {
|
val key = splitLine[0].trim()
|
||||||
generalSectionName
|
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 {
|
fun getSettingsFile(fileName: String): DocumentFile {
|
||||||
val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory))
|
val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory))
|
||||||
@ -190,96 +242,20 @@ object SettingsFile {
|
|||||||
return configDirectory!!.findFile("$fileName.ini")!!
|
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 root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory))
|
||||||
val configDirectory = root!!.findFile("GameSettings")
|
val configDir = root?.findFile("config") ?: return null
|
||||||
return configDirectory!!.findFile("$gameId.ini")!!
|
val customDir = configDir.findFile("custom") ?: return null
|
||||||
|
return customDir.findFile("$gameId.ini")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
|
private fun getOrCreateCustomGameSettingsFile(gameId: String): DocumentFile {
|
||||||
var sectionName: String = line.substring(1, line.length - 1)
|
val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory))!!
|
||||||
if (isCustomGame) {
|
val configDir = root.findFile("config") ?: root.createDirectory("config")!!
|
||||||
sectionName = mapSectionNameToIni(sectionName)
|
val customDir = configDir.findFile("custom") ?: configDir.createDirectory("custom")!!
|
||||||
}
|
return customDir.findFile("$gameId.ini")
|
||||||
return SettingSection(sectionName)
|
?: customDir.createFile("*/*", "$gameId.ini")!!
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,7 +44,6 @@ import androidx.drawerlayout.widget.DrawerLayout
|
|||||||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
@ -70,7 +69,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.BooleanSetting
|
||||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
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.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.ui.SettingsActivity
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
import org.citra.citra_emu.model.Game
|
import org.citra.citra_emu.model.Game
|
||||||
@ -78,7 +76,6 @@ import org.citra.citra_emu.utils.BuildUtil
|
|||||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||||
import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState
|
import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState
|
||||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
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.GameHelper
|
||||||
import org.citra.citra_emu.utils.GameIconUtils
|
import org.citra.citra_emu.utils.GameIconUtils
|
||||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||||
@ -93,7 +90,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
private lateinit var emulationState: EmulationState
|
private lateinit var emulationState: EmulationState
|
||||||
private var perfStatsUpdater: Runnable? = null
|
private var perfStatsUpdater: Runnable? = null
|
||||||
|
|
||||||
private lateinit var emulationActivity: EmulationActivity
|
private var emulationActivity: EmulationActivity? = null
|
||||||
|
|
||||||
private var _binding: FragmentEmulationBinding? = null
|
private var _binding: FragmentEmulationBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
@ -104,8 +101,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||||
|
|
||||||
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
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 onPause = Runnable{ togglePause() }
|
||||||
private val onShutdown = Runnable{ emulationState.stop() }
|
private val onShutdown = Runnable{ emulationState.stop() }
|
||||||
@ -184,7 +179,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
retainInstance = true
|
retainInstance = true
|
||||||
emulationState = EmulationState(game.path)
|
emulationState = EmulationState(game.path)
|
||||||
emulationActivity = requireActivity() as EmulationActivity
|
emulationActivity = requireActivity() as EmulationActivity
|
||||||
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settings)
|
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, Settings.settings)
|
||||||
EmulationLifecycleUtil.addPauseResumeHook(onPause)
|
EmulationLifecycleUtil.addPauseResumeHook(onPause)
|
||||||
EmulationLifecycleUtil.addShutdownHook(onShutdown)
|
EmulationLifecycleUtil.addShutdownHook(onShutdown)
|
||||||
}
|
}
|
||||||
@ -211,7 +206,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
if (requireActivity().isFinishing) {
|
if (requireActivity().isFinishing) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
binding.surfaceInputOverlay.initializeSettings(Settings.settings)
|
||||||
binding.surfaceEmulation.holder.addCallback(this)
|
binding.surfaceEmulation.holder.addCallback(this)
|
||||||
binding.doneControlConfig.setOnClickListener {
|
binding.doneControlConfig.setOnClickListener {
|
||||||
binding.doneControlConfig.visibility = View.GONE
|
binding.doneControlConfig.visibility = View.GONE
|
||||||
@ -221,7 +216,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
// Show/hide the "Stats" overlay
|
// Show/hide the "Stats" overlay
|
||||||
updateShowPerformanceOverlay()
|
updateShowPerformanceOverlay()
|
||||||
|
|
||||||
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
|
val position = Settings.settings.get(IntSetting.PERFORMANCE_OVERLAY_POSITION)
|
||||||
updateStatsPosition(position)
|
updateStatsPosition(position)
|
||||||
|
|
||||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||||
@ -389,6 +384,27 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.menu_application_settings -> {
|
||||||
|
val titleId = NativeLibrary.getRunningTitleId()
|
||||||
|
if (titleId != 0L) {
|
||||||
|
val gameId = java.lang.String.format("%016X", titleId)
|
||||||
|
SettingsActivity.launch(
|
||||||
|
requireContext(),
|
||||||
|
SettingsFile.FILE_NAME_CONFIG,
|
||||||
|
gameId
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Fallback: open global settings if title id unknown
|
||||||
|
SettingsActivity.launch(
|
||||||
|
requireContext(),
|
||||||
|
SettingsFile.FILE_NAME_CONFIG,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
R.id.menu_exit -> {
|
R.id.menu_exit -> {
|
||||||
emulationState.pause()
|
emulationState.pause()
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
@ -508,7 +524,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
emulationState.unpause()
|
emulationState.unpause()
|
||||||
|
|
||||||
// If the overlay is enabled, we need to update the position if changed
|
// 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)
|
updateStatsPosition(position)
|
||||||
|
|
||||||
binding.inGameMenu.menu.findItem(R.id.menu_emulation_pause)?.let { menuItem ->
|
binding.inGameMenu.menu.findItem(R.id.menu_emulation_pause)?.let { menuItem ->
|
||||||
@ -523,7 +539,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (DirectoryInitialization.areCitraDirectoriesReady()) {
|
if (DirectoryInitialization.areCitraDirectoriesReady()) {
|
||||||
emulationState.run(emulationActivity.isActivityRecreated)
|
emulationState.run(emulationActivity!!.isActivityRecreated)
|
||||||
} else {
|
} else {
|
||||||
setupCitraDirectoriesThenStartEmulation()
|
setupCitraDirectoriesThenStartEmulation()
|
||||||
}
|
}
|
||||||
@ -538,6 +554,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetach() {
|
override fun onDetach() {
|
||||||
|
emulationActivity = null
|
||||||
NativeLibrary.clearEmulationActivity()
|
NativeLibrary.clearEmulationActivity()
|
||||||
super.onDetach()
|
super.onDetach()
|
||||||
}
|
}
|
||||||
@ -557,7 +574,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
if (directoryInitializationState ===
|
if (directoryInitializationState ===
|
||||||
DirectoryInitializationState.CITRA_DIRECTORIES_INITIALIZED
|
DirectoryInitializationState.CITRA_DIRECTORIES_INITIALIZED
|
||||||
) {
|
) {
|
||||||
emulationState.run(emulationActivity.isActivityRecreated)
|
emulationState.run(emulationActivity!!.isActivityRecreated)
|
||||||
} else if (directoryInitializationState ===
|
} else if (directoryInitializationState ===
|
||||||
DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED
|
DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED
|
||||||
) {
|
) {
|
||||||
@ -707,7 +724,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
popupMenu.menu.apply {
|
popupMenu.menu.apply {
|
||||||
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
|
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
|
||||||
findItem(R.id.menu_performance_overlay_show).isChecked =
|
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_haptic_feedback).isChecked = EmulationMenuSettings.hapticFeedback
|
||||||
findItem(R.id.menu_emulation_joystick_rel_center).isChecked =
|
findItem(R.id.menu_emulation_joystick_rel_center).isChecked =
|
||||||
EmulationMenuSettings.joystickRelCenter
|
EmulationMenuSettings.joystickRelCenter
|
||||||
@ -724,8 +741,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_performance_overlay_show -> {
|
R.id.menu_performance_overlay_show -> {
|
||||||
BooleanSetting.PERF_OVERLAY_ENABLE.boolean = !BooleanSetting.PERF_OVERLAY_ENABLE.boolean
|
Settings.settings.update(BooleanSetting.PERF_OVERLAY_ENABLE,
|
||||||
settings.saveSetting(BooleanSetting.PERF_OVERLAY_ENABLE, SettingsFile.FILE_NAME_CONFIG)
|
Settings.settings.get(BooleanSetting.PERF_OVERLAY_ENABLE))
|
||||||
|
SettingsFile.saveSetting(BooleanSetting.PERF_OVERLAY_ENABLE, Settings.settings)
|
||||||
updateShowPerformanceOverlay()
|
updateShowPerformanceOverlay()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -875,7 +893,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
popupMenu.setOnMenuItemClickListener {
|
popupMenu.setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.menu_emulation_amiibo_load -> {
|
R.id.menu_emulation_amiibo_load -> {
|
||||||
emulationActivity.openAmiiboFileLauncher.launch(false)
|
emulationActivity!!.openAmiiboFileLauncher.launch(false)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -921,7 +939,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
|
|
||||||
popupMenu.menuInflater.inflate(R.menu.menu_landscape_screen_layout, popupMenu.menu)
|
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 ->
|
ScreenLayout.ORIGINAL.int ->
|
||||||
R.id.menu_screen_layout_original
|
R.id.menu_screen_layout_original
|
||||||
|
|
||||||
@ -993,7 +1011,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
|
|
||||||
popupMenu.menuInflater.inflate(R.menu.menu_portrait_screen_layout, popupMenu.menu)
|
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 ->
|
PortraitScreenLayout.TOP_FULL_WIDTH.int ->
|
||||||
R.id.menu_portrait_layout_top_full
|
R.id.menu_portrait_layout_top_full
|
||||||
PortraitScreenLayout.ORIGINAL.int ->
|
PortraitScreenLayout.ORIGINAL.int ->
|
||||||
@ -1260,7 +1278,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BooleanSetting.PERF_OVERLAY_ENABLE.boolean) {
|
if (Settings.settings.get(BooleanSetting.PERF_OVERLAY_ENABLE)) {
|
||||||
val SYSTEM_FPS = 0
|
val SYSTEM_FPS = 0
|
||||||
val FPS = 1
|
val FPS = 1
|
||||||
val SPEED = 2
|
val SPEED = 2
|
||||||
@ -1275,11 +1293,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
val perfStats = NativeLibrary.getPerfStats()
|
val perfStats = NativeLibrary.getPerfStats()
|
||||||
val dividerString = "\u00A0\u2502 "
|
val dividerString = "\u00A0\u2502 "
|
||||||
if (perfStats[FPS] > 0) {
|
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()))
|
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)
|
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||||
sb.append(
|
sb.append(
|
||||||
String.format(
|
String.format(
|
||||||
@ -1294,7 +1312,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)
|
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||||
sb.append(
|
sb.append(
|
||||||
String.format(
|
String.format(
|
||||||
@ -1304,14 +1322,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)
|
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||||
val appRamUsage =
|
val appRamUsage =
|
||||||
File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 / 1000000
|
File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 / 1000000
|
||||||
sb.append("Process\u00A0RAM:\u00A0$appRamUsage\u00A0MB")
|
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)
|
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
val activityManager =
|
val activityManager =
|
||||||
@ -1324,14 +1342,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)
|
if (sb.isNotEmpty()) sb.append(dividerString)
|
||||||
val batteryTemp = getBatteryTemperature()
|
val batteryTemp = getBatteryTemperature()
|
||||||
val tempF = celsiusToFahrenheit(batteryTemp)
|
val tempF = celsiusToFahrenheit(batteryTemp)
|
||||||
sb.append(String.format("%.1f°C/%.1f°F", batteryTemp, tempF))
|
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)
|
binding.performanceOverlayShowText.setBackgroundResource(R.color.citra_transparent_black)
|
||||||
} else {
|
} else {
|
||||||
binding.performanceOverlayShowText.setBackgroundResource(0)
|
binding.performanceOverlayShowText.setBackgroundResource(0)
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import androidx.preference.PreferenceManager
|
|||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.NativeLibrary
|
import org.citra.citra_emu.NativeLibrary
|
||||||
import org.citra.citra_emu.R
|
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.EmulationMenuSettings
|
||||||
import org.citra.citra_emu.utils.TurboHelper
|
import org.citra.citra_emu.utils.TurboHelper
|
||||||
import java.lang.NullPointerException
|
import java.lang.NullPointerException
|
||||||
@ -45,7 +46,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||||||
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
|
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
|
||||||
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||||
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = 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.
|
// Stores the ID of the pointer that interacted with the 3DS touchscreen.
|
||||||
private var touchscreenPointerId = -1
|
private var touchscreenPointerId = -1
|
||||||
@ -71,6 +72,10 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||||||
requestFocus()
|
requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun initializeSettings(settings: Settings) {
|
||||||
|
this.settings = settings
|
||||||
|
}
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
super.draw(canvas)
|
super.draw(canvas)
|
||||||
overlayButtons.forEach { it.draw(canvas) }
|
overlayButtons.forEach { it.draw(canvas) }
|
||||||
@ -173,7 +178,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||||||
swapScreen()
|
swapScreen()
|
||||||
}
|
}
|
||||||
else if (button.id == NativeLibrary.ButtonType.BUTTON_TURBO && button.status == NativeLibrary.ButtonState.PRESSED) {
|
else if (button.id == NativeLibrary.ButtonType.BUTTON_TURBO && button.status == NativeLibrary.ButtonState.PRESSED) {
|
||||||
TurboHelper.toggleTurbo(true)
|
TurboHelper.toggleTurbo(true, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeLibrary.onGamePadEvent(
|
NativeLibrary.onGamePadEvent(
|
||||||
|
|||||||
@ -48,7 +48,6 @@ import org.citra.citra_emu.R
|
|||||||
import org.citra.citra_emu.contracts.OpenFileResultContract
|
import org.citra.citra_emu.contracts.OpenFileResultContract
|
||||||
import org.citra.citra_emu.databinding.ActivityMainBinding
|
import org.citra.citra_emu.databinding.ActivityMainBinding
|
||||||
import org.citra.citra_emu.features.settings.model.Settings
|
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.ui.SettingsActivity
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
import org.citra.citra_emu.fragments.GrantMissingFilesystemPermissionFragment
|
import org.citra.citra_emu.fragments.GrantMissingFilesystemPermissionFragment
|
||||||
@ -72,7 +71,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
|
|
||||||
private val homeViewModel: HomeViewModel by viewModels()
|
private val homeViewModel: HomeViewModel by viewModels()
|
||||||
private val gamesViewModel: GamesViewModel by viewModels()
|
private val gamesViewModel: GamesViewModel by viewModels()
|
||||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
|
||||||
|
|
||||||
override var themeId: Int = 0
|
override var themeId: Int = 0
|
||||||
|
|
||||||
@ -95,13 +93,17 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
if (PermissionsHandler.hasWriteAccess(applicationContext) &&
|
if (PermissionsHandler.hasWriteAccess(applicationContext) &&
|
||||||
DirectoryInitialization.areCitraDirectoriesReady() &&
|
DirectoryInitialization.areCitraDirectoriesReady() &&
|
||||||
!CitraDirectoryUtils.needToUpdateManually()) {
|
!CitraDirectoryUtils.needToUpdateManually()) {
|
||||||
settingsViewModel.settings.loadSettings()
|
// load the global settings from the config file at program launch
|
||||||
|
// if the directory is available
|
||||||
|
SettingsFile.loadSettings(Settings.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeUtil.ThemeChangeListener(this)
|
ThemeUtil.ThemeChangeListener(this)
|
||||||
ThemeUtil.setTheme(this)
|
ThemeUtil.setTheme(this)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@ -273,6 +275,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
fun finishSetup(navController: NavController) {
|
fun finishSetup(navController: NavController) {
|
||||||
navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
|
navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
|
||||||
(binding.navigationView as NavigationBarView).setupWithNavController(navController)
|
(binding.navigationView as NavigationBarView).setupWithNavController(navController)
|
||||||
|
SettingsFile.loadSettings(Settings.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpNavigation(savedInstanceState: Bundle?, navController: NavController) {
|
private fun setUpNavigation(savedInstanceState: Bundle?, navController: NavController) {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import org.citra.citra_emu.CitraApplication
|
|||||||
import org.citra.citra_emu.NativeLibrary
|
import org.citra.citra_emu.NativeLibrary
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||||
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
|
|
||||||
object TurboHelper {
|
object TurboHelper {
|
||||||
private var turboSpeedEnabled = false
|
private var turboSpeedEnabled = false
|
||||||
@ -17,12 +18,12 @@ object TurboHelper {
|
|||||||
return turboSpeedEnabled
|
return turboSpeedEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reloadTurbo(showToast: Boolean) {
|
fun reloadTurbo(showToast: Boolean, settings: Settings) {
|
||||||
val context = CitraApplication.appContext
|
val context = CitraApplication.appContext
|
||||||
val toastMessage: String
|
val toastMessage: String
|
||||||
|
|
||||||
if (turboSpeedEnabled) {
|
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)
|
toastMessage = context.getString(R.string.turbo_enabled_toast)
|
||||||
} else {
|
} else {
|
||||||
NativeLibrary.disableTemporaryFrameLimit()
|
NativeLibrary.disableTemporaryFrameLimit()
|
||||||
@ -34,12 +35,12 @@ object TurboHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTurboEnabled(state: Boolean, showToast: Boolean) {
|
fun setTurboEnabled(state: Boolean, showToast: Boolean, settings: Settings) {
|
||||||
turboSpeedEnabled = state
|
turboSpeedEnabled = state
|
||||||
reloadTurbo(showToast)
|
reloadTurbo(showToast, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleTurbo(showToast: Boolean) {
|
fun toggleTurbo(showToast: Boolean, settings: Settings) {
|
||||||
setTurboEnabled(!TurboHelper.isTurboSpeedEnabled(), showToast)
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -7,9 +7,14 @@ package org.citra.citra_emu.viewmodel
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
|
|
||||||
class EmulationViewModel : ViewModel() {
|
class EmulationViewModel : ViewModel() {
|
||||||
val emulationStarted get() = _emulationStarted.asStateFlow()
|
val emulationStarted get() = _emulationStarted.asStateFlow()
|
||||||
|
// convenience shortcut for
|
||||||
|
val settings = Settings.settings
|
||||||
|
|
||||||
private val _emulationStarted = MutableStateFlow(false)
|
private val _emulationStarted = MutableStateFlow(false)
|
||||||
|
|
||||||
val shaderProgress get() = _shaderProgress.asStateFlow()
|
val shaderProgress get() = _shaderProgress.asStateFlow()
|
||||||
@ -21,6 +26,12 @@ class EmulationViewModel : ViewModel() {
|
|||||||
val shaderMessage get() = _shaderMessage.asStateFlow()
|
val shaderMessage get() = _shaderMessage.asStateFlow()
|
||||||
private val _shaderMessage = MutableStateFlow("")
|
private val _shaderMessage = MutableStateFlow("")
|
||||||
|
|
||||||
|
|
||||||
|
/** Used for the initial load of settings. Call rebuild for later creations. */
|
||||||
|
fun loadSettings(titleId: Long) {
|
||||||
|
if (settings.getAllGlobal().isNotEmpty()) return //already loaded
|
||||||
|
SettingsFile.loadSettings(settings, String.format("%016X", titleId))
|
||||||
|
}
|
||||||
fun setShaderProgress(progress: Int) {
|
fun setShaderProgress(progress: Int) {
|
||||||
_shaderProgress.value = progress
|
_shaderProgress.value = progress
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,28 +79,65 @@ static const std::array<int, Settings::NativeAnalog::NumAnalogs> default_analogs
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
|
std::string Config::GetSetting(const std::string& group, Settings::Setting<std::string>& setting) {
|
||||||
std::string setting_value =
|
std::string setting_value = setting.GetDefault();
|
||||||
android_config->Get(group, setting.GetLabel(), setting.GetDefault());
|
if (per_game_config && per_game_config->HasValue(group, setting.GetLabel())) {
|
||||||
|
setting_value = per_game_config->Get(group, setting.GetLabel(), setting_value);
|
||||||
|
} else if (android_config) {
|
||||||
|
setting_value = android_config->Get(group, setting.GetLabel(), setting_value);
|
||||||
|
}
|
||||||
if (setting_value.empty()) {
|
if (setting_value.empty()) {
|
||||||
setting_value = setting.GetDefault();
|
setting_value = setting.GetDefault();
|
||||||
}
|
}
|
||||||
setting = std::move(setting_value);
|
return setting_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
|
||||||
|
setting = std::move(GetSetting(group, setting));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
bool Config::GetSetting(const std::string& group, Settings::Setting<bool>& setting) {
|
||||||
|
bool value = setting.GetDefault();
|
||||||
|
if (per_game_config && per_game_config->HasValue(group, setting.GetLabel())) {
|
||||||
|
value = per_game_config->GetBoolean(group, setting.GetLabel(), value);
|
||||||
|
} else if (android_config) {
|
||||||
|
value = android_config->GetBoolean(group, setting.GetLabel(), value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
|
void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
|
||||||
setting = android_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
|
setting = GetSetting(group, setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: figure out why ranged isn't being used
|
||||||
|
template <typename Type, bool ranged>
|
||||||
|
Type Config::GetSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
|
||||||
|
if constexpr (std::is_floating_point_v<Type>) {
|
||||||
|
double value = static_cast<double>(setting.GetDefault());
|
||||||
|
if (per_game_config && per_game_config->HasValue(group, setting.GetLabel())) {
|
||||||
|
value = per_game_config->GetReal(group, setting.GetLabel(), value);
|
||||||
|
} else if (android_config) {
|
||||||
|
value = android_config->GetReal(group, setting.GetLabel(), value);
|
||||||
|
}
|
||||||
|
return static_cast<Type>(value);
|
||||||
|
} else {
|
||||||
|
long value = static_cast<long>(setting.GetDefault());
|
||||||
|
if (per_game_config && per_game_config->HasValue(group, setting.GetLabel())) {
|
||||||
|
value = per_game_config->GetInteger(group, setting.GetLabel(), value);
|
||||||
|
} else if (android_config) {
|
||||||
|
value = android_config->GetInteger(group, setting.GetLabel(), value);
|
||||||
|
}
|
||||||
|
return static_cast<Type>(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type, bool ranged>
|
template <typename Type, bool ranged>
|
||||||
void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
|
void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
|
||||||
if constexpr (std::is_floating_point_v<Type>) {
|
setting = GetSetting(group, setting);
|
||||||
setting = android_config->GetReal(group, setting.GetLabel(), setting.GetDefault());
|
|
||||||
} else {
|
|
||||||
setting = static_cast<Type>(android_config->GetInteger(
|
|
||||||
group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::ReadValues() {
|
void Config::ReadValues() {
|
||||||
@ -139,9 +176,8 @@ void Config::ReadValues() {
|
|||||||
ReadSetting("Core", Settings::values.cpu_clock_percentage);
|
ReadSetting("Core", Settings::values.cpu_clock_percentage);
|
||||||
|
|
||||||
// Renderer
|
// Renderer
|
||||||
Settings::values.use_gles = android_config->GetBoolean("Renderer", "use_gles", true);
|
ReadSetting("Renderer",Settings::values.use_gles);
|
||||||
Settings::values.shaders_accurate_mul =
|
ReadSetting("Renderer",Settings::values.shaders_accurate_mul);
|
||||||
android_config->GetBoolean("Renderer", "shaders_accurate_mul", false);
|
|
||||||
ReadSetting("Renderer", Settings::values.graphics_api);
|
ReadSetting("Renderer", Settings::values.graphics_api);
|
||||||
ReadSetting("Renderer", Settings::values.async_presentation);
|
ReadSetting("Renderer", Settings::values.async_presentation);
|
||||||
ReadSetting("Renderer", Settings::values.async_shader_compilation);
|
ReadSetting("Renderer", Settings::values.async_shader_compilation);
|
||||||
@ -156,7 +192,14 @@ void Config::ReadValues() {
|
|||||||
ReadSetting("Renderer", Settings::values.texture_sampling);
|
ReadSetting("Renderer", Settings::values.texture_sampling);
|
||||||
ReadSetting("Renderer", Settings::values.turbo_limit);
|
ReadSetting("Renderer", Settings::values.turbo_limit);
|
||||||
// Workaround to map Android setting for enabling the frame limiter to the format Citra expects
|
// Workaround to map Android setting for enabling the frame limiter to the format Citra expects
|
||||||
if (android_config->GetBoolean("Renderer", "use_frame_limit", true)) {
|
// TODO: test this!
|
||||||
|
bool use_frame_limit = false;
|
||||||
|
if (per_game_config && per_game_config->HasValue("Renderer", "use_frame_limit")) {
|
||||||
|
use_frame_limit = per_game_config->GetBoolean("Renderer", "use_frame_limit", true);
|
||||||
|
} else if (android_config) {
|
||||||
|
use_frame_limit = android_config->GetBoolean("Renderer", "use_frame_limit", true);
|
||||||
|
}
|
||||||
|
if (use_frame_limit) {
|
||||||
ReadSetting("Renderer", Settings::values.frame_limit);
|
ReadSetting("Renderer", Settings::values.frame_limit);
|
||||||
} else {
|
} else {
|
||||||
Settings::values.frame_limit = 0;
|
Settings::values.frame_limit = 0;
|
||||||
@ -183,21 +226,10 @@ void Config::ReadValues() {
|
|||||||
ReadSetting("Renderer", Settings::values.swap_eyes_3d);
|
ReadSetting("Renderer", Settings::values.swap_eyes_3d);
|
||||||
ReadSetting("Renderer", Settings::values.render_3d_which_display);
|
ReadSetting("Renderer", Settings::values.render_3d_which_display);
|
||||||
// Layout
|
// Layout
|
||||||
// Somewhat inelegant solution to ensure layout value is between 0 and 5 on read
|
|
||||||
// since older config files may have other values
|
ReadSetting("Layout",Settings::values.layout_option);
|
||||||
int layoutInt = (int)android_config->GetInteger(
|
ReadSetting("Layout",Settings::values.screen_gap);
|
||||||
"Layout", "layout_option", static_cast<int>(Settings::LayoutOption::LargeScreen));
|
ReadSetting("Layout", Settings::values.small_screen_position);
|
||||||
if (layoutInt < 0 || layoutInt > 5) {
|
|
||||||
layoutInt = static_cast<int>(Settings::LayoutOption::LargeScreen);
|
|
||||||
}
|
|
||||||
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layoutInt);
|
|
||||||
Settings::values.screen_gap =
|
|
||||||
static_cast<int>(android_config->GetReal("Layout", "screen_gap", 0));
|
|
||||||
Settings::values.large_screen_proportion =
|
|
||||||
static_cast<float>(android_config->GetReal("Layout", "large_screen_proportion", 2.25));
|
|
||||||
Settings::values.small_screen_position = static_cast<Settings::SmallScreenPosition>(
|
|
||||||
android_config->GetInteger("Layout", "small_screen_position",
|
|
||||||
static_cast<int>(Settings::SmallScreenPosition::TopRight)));
|
|
||||||
ReadSetting("Layout", Settings::values.screen_gap);
|
ReadSetting("Layout", Settings::values.screen_gap);
|
||||||
ReadSetting("Layout", Settings::values.custom_top_x);
|
ReadSetting("Layout", Settings::values.custom_top_x);
|
||||||
ReadSetting("Layout", Settings::values.custom_top_y);
|
ReadSetting("Layout", Settings::values.custom_top_y);
|
||||||
@ -212,14 +244,8 @@ void Config::ReadValues() {
|
|||||||
ReadSetting("Layout", Settings::values.cardboard_x_shift);
|
ReadSetting("Layout", Settings::values.cardboard_x_shift);
|
||||||
ReadSetting("Layout", Settings::values.cardboard_y_shift);
|
ReadSetting("Layout", Settings::values.cardboard_y_shift);
|
||||||
ReadSetting("Layout", Settings::values.upright_screen);
|
ReadSetting("Layout", Settings::values.upright_screen);
|
||||||
|
ReadSetting("Layout", Settings::values.portrait_layout_option);
|
||||||
Settings::values.portrait_layout_option =
|
ReadSetting("Layout", Settings::values.secondary_display_layout);
|
||||||
static_cast<Settings::PortraitLayoutOption>(android_config->GetInteger(
|
|
||||||
"Layout", "portrait_layout_option",
|
|
||||||
static_cast<int>(Settings::PortraitLayoutOption::PortraitTopFullWidth)));
|
|
||||||
Settings::values.secondary_display_layout = static_cast<Settings::SecondaryDisplayLayout>(
|
|
||||||
android_config->GetInteger("Layout", Settings::HKeys::secondary_display_layout.c_str(),
|
|
||||||
static_cast<int>(Settings::SecondaryDisplayLayout::None)));
|
|
||||||
ReadSetting("Layout", Settings::values.custom_portrait_top_x);
|
ReadSetting("Layout", Settings::values.custom_portrait_top_x);
|
||||||
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
|
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
|
||||||
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
|
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
|
||||||
@ -349,3 +375,37 @@ void Config::Reload() {
|
|||||||
LoadINI(DefaultINI::android_config_default_file_content);
|
LoadINI(DefaultINI::android_config_default_file_content);
|
||||||
ReadValues();
|
ReadValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Config::LoadPerGameConfig(u64 title_id, const std::string& fallback_name) {
|
||||||
|
// Determine file name
|
||||||
|
std::string name;
|
||||||
|
if (title_id != 0) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << std::uppercase << std::hex << std::setw(16) << std::setfill('0') << title_id;
|
||||||
|
name = ss.str();
|
||||||
|
} else {
|
||||||
|
name = fallback_name;
|
||||||
|
}
|
||||||
|
if (name.empty()) {
|
||||||
|
per_game_config.reset();
|
||||||
|
per_game_config_loc.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto base = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir);
|
||||||
|
per_game_config_loc = base + "custom/" + name + ".ini";
|
||||||
|
|
||||||
|
std::string ini_buffer;
|
||||||
|
FileUtil::ReadFileToString(true, per_game_config_loc, ini_buffer);
|
||||||
|
if (!ini_buffer.empty()) {
|
||||||
|
per_game_config = std::make_unique<INIReader>(ini_buffer.c_str(), ini_buffer.size());
|
||||||
|
if (per_game_config->ParseError() < 0) {
|
||||||
|
per_game_config.reset();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
per_game_config.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-apply values so that per-game overrides (if any) take effect immediately.
|
||||||
|
ReadValues();
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ class Config {
|
|||||||
private:
|
private:
|
||||||
std::unique_ptr<INIReader> android_config;
|
std::unique_ptr<INIReader> android_config;
|
||||||
std::string android_config_loc;
|
std::string android_config_loc;
|
||||||
|
std::unique_ptr<INIReader> per_game_config;
|
||||||
|
std::string per_game_config_loc;
|
||||||
|
|
||||||
bool LoadINI(const std::string& default_contents = "", bool retry = true);
|
bool LoadINI(const std::string& default_contents = "", bool retry = true);
|
||||||
void ReadValues();
|
void ReadValues();
|
||||||
@ -23,14 +25,27 @@ public:
|
|||||||
~Config();
|
~Config();
|
||||||
|
|
||||||
void Reload();
|
void Reload();
|
||||||
|
// Load a per-game config overlay by title id or fallback name. Does not create files.
|
||||||
|
void LoadPerGameConfig(u64 title_id, const std::string& fallback_name = "");
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Applies a value read from the android_config to a Setting.
|
* Applies a value read from the android_config to a Setting.
|
||||||
*
|
*
|
||||||
* @param group The name of the INI group
|
* @param group The name of the INI group
|
||||||
* @param setting The yuzu setting to modify
|
* @param setting The setting to modify
|
||||||
*/
|
*/
|
||||||
template <typename Type, bool ranged>
|
template <typename Type, bool ranged>
|
||||||
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
|
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a value honoring per_game config, and returns it.
|
||||||
|
* Does not modify the setting.
|
||||||
|
*
|
||||||
|
* @param group The name of the INI group
|
||||||
|
* @param setting The setting to modify
|
||||||
|
*/
|
||||||
|
template <typename Type, bool ranged>
|
||||||
|
Type GetSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -191,6 +191,28 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
|
|
||||||
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||||
EGLContext* shared_context;
|
EGLContext* shared_context;
|
||||||
|
|
||||||
|
|
||||||
|
// Load game-specific settings overlay if available
|
||||||
|
u64 program_id{};
|
||||||
|
FileUtil::SetCurrentRomPath(filepath);
|
||||||
|
auto app_loader = Loader::GetLoader(filepath);
|
||||||
|
if (app_loader) {
|
||||||
|
app_loader->ReadProgramId(program_id);
|
||||||
|
system.RegisterAppLoaderEarly(app_loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forces a config reload on game boot, if the user changed settings in the UI
|
||||||
|
Config global_config{};
|
||||||
|
|
||||||
|
// Use filename as fallback if title id is zero (e.g., homebrew)
|
||||||
|
const std::string fallback_name =
|
||||||
|
program_id == 0 ? std::string(FileUtil::GetFilename(filepath)) : std::string{};
|
||||||
|
global_config.LoadPerGameConfig(program_id, fallback_name);
|
||||||
|
system.ApplySettings();
|
||||||
|
Settings::LogSettings();
|
||||||
|
|
||||||
|
|
||||||
switch (graphics_api) {
|
switch (graphics_api) {
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
case Settings::GraphicsAPI::OpenGL:
|
case Settings::GraphicsAPI::OpenGL:
|
||||||
@ -228,18 +250,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forces a config reload on game boot, if the user changed settings in the UI
|
|
||||||
Config{};
|
|
||||||
// Replace with game-specific settings
|
|
||||||
u64 program_id{};
|
|
||||||
FileUtil::SetCurrentRomPath(filepath);
|
|
||||||
auto app_loader = Loader::GetLoader(filepath);
|
|
||||||
if (app_loader) {
|
|
||||||
app_loader->ReadProgramId(program_id);
|
|
||||||
system.RegisterAppLoaderEarly(app_loader);
|
|
||||||
}
|
|
||||||
system.ApplySettings();
|
|
||||||
Settings::LogSettings();
|
|
||||||
|
|
||||||
Camera::RegisterFactory("image", std::make_unique<Camera::StillImage::Factory>());
|
Camera::RegisterFactory("image", std::make_unique<Camera::StillImage::Factory>());
|
||||||
|
|
||||||
@ -913,13 +924,18 @@ void Java_org_citra_citra_1emu_NativeLibrary_logUserDirectory(JNIEnv* env,
|
|||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
Config{};
|
Config cfg{};
|
||||||
Core::System& system{Core::System::GetInstance()};
|
Core::System& system{Core::System::GetInstance()};
|
||||||
|
|
||||||
// Replace with game-specific settings
|
// Load game-specific settings overlay (if a game is running)
|
||||||
if (system.IsPoweredOn()) {
|
if (system.IsPoweredOn()) {
|
||||||
u64 program_id{};
|
u64 program_id{};
|
||||||
system.GetAppLoader().ReadProgramId(program_id);
|
system.GetAppLoader().ReadProgramId(program_id);
|
||||||
|
// Use the registered ROM path (if any) to derive a fallback name
|
||||||
|
const std::string current_rom_path = FileUtil::GetCurrentRomPath();
|
||||||
|
const std::string fallback_name =
|
||||||
|
program_id == 0 ? std::string(FileUtil::GetFilename(current_rom_path)) : std::string{};
|
||||||
|
cfg.LoadPerGameConfig(program_id, fallback_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
system.ApplySettings();
|
system.ApplySettings();
|
||||||
|
|||||||
@ -180,6 +180,15 @@
|
|||||||
android:contentDescription="@string/cheats"
|
android:contentDescription="@string/cheats"
|
||||||
android:text="@string/cheats" />
|
android:text="@string/cheats" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/application_settings"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:contentDescription="@string/application_settings"
|
||||||
|
android:text="@string/application_settings" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/insert_cartridge_button"
|
android:id="@+id/insert_cartridge_button"
|
||||||
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||||
|
|||||||
@ -62,6 +62,15 @@
|
|||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
tools:text="1x" />
|
tools:text="1x" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_use_global"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="@string/use_global"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@ -46,6 +46,15 @@
|
|||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
tools:text="@string/frame_limit_enable_description" />
|
tools:text="@string/frame_limit_enable_description" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_use_global"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="@string/use_global"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@ -57,6 +57,11 @@
|
|||||||
android:icon="@drawable/ic_settings"
|
android:icon="@drawable/ic_settings"
|
||||||
android:title="@string/preferences_settings" />
|
android:title="@string/preferences_settings" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_application_settings"
|
||||||
|
android:icon="@drawable/ic_settings"
|
||||||
|
android:title="@string/application_settings" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_exit"
|
android:id="@+id/menu_exit"
|
||||||
android:icon="@drawable/ic_exit"
|
android:icon="@drawable/ic_exit"
|
||||||
|
|||||||
@ -522,6 +522,8 @@
|
|||||||
<string name="menu_emulation_amiibo">Amiibo</string>
|
<string name="menu_emulation_amiibo">Amiibo</string>
|
||||||
<string name="menu_emulation_amiibo_load">Load</string>
|
<string name="menu_emulation_amiibo_load">Load</string>
|
||||||
<string name="menu_emulation_amiibo_remove">Remove</string>
|
<string name="menu_emulation_amiibo_remove">Remove</string>
|
||||||
|
<string name="application_settings">Custom Settings</string>
|
||||||
|
<string name="use_global">Customized: Revert to Global</string>
|
||||||
<string name="select_amiibo">Select Amiibo File</string>
|
<string name="select_amiibo">Select Amiibo File</string>
|
||||||
<string name="amiibo_load_error">Error Loading Amiibo</string>
|
<string name="amiibo_load_error">Error Loading Amiibo</string>
|
||||||
<string name="amiibo_load_error_message">While loading the specified Amiibo file, an error occurred. Please check that the file is correct.</string>
|
<string name="amiibo_load_error_message">While loading the specified Amiibo file, an error occurred. Please check that the file is correct.</string>
|
||||||
|
|||||||
@ -1037,6 +1037,10 @@ void SetCurrentRomPath(const std::string& path) {
|
|||||||
g_currentRomPath = path;
|
g_currentRomPath = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GetCurrentRomPath() {
|
||||||
|
return g_currentRomPath;
|
||||||
|
}
|
||||||
|
|
||||||
bool StringReplace(std::string& haystack, const std::string& a, const std::string& b, bool swap) {
|
bool StringReplace(std::string& haystack, const std::string& a, const std::string& b, bool swap) {
|
||||||
const auto& needle = swap ? b : a;
|
const auto& needle = swap ? b : a;
|
||||||
const auto& replacement = swap ? a : b;
|
const auto& replacement = swap ? a : b;
|
||||||
|
|||||||
@ -213,6 +213,7 @@ bool SetCurrentDir(const std::string& directory);
|
|||||||
void SetUserPath(const std::string& path = "");
|
void SetUserPath(const std::string& path = "");
|
||||||
|
|
||||||
void SetCurrentRomPath(const std::string& path);
|
void SetCurrentRomPath(const std::string& path);
|
||||||
|
[[nodiscard]] std::string GetCurrentRomPath();
|
||||||
|
|
||||||
// Returns a pointer to a string with a Citra data dir in the user's home
|
// Returns a pointer to a string with a Citra data dir in the user's home
|
||||||
// directory. To be used in "multi-user" mode (that is, installed).
|
// directory. To be used in "multi-user" mode (that is, installed).
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user