android: Add per-game settings
This commit is contained in:
parent
e975f3cde9
commit
2fce812026
|
@ -12,6 +12,7 @@ import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
|||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
/**
|
||||
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
|
||||
|
@ -30,9 +31,19 @@ abstract class SettingsItem(
|
|||
val isEditable: Boolean
|
||||
get() {
|
||||
if (!NativeLibrary.isRunning()) return true
|
||||
|
||||
// Prevent editing settings that were modified in per-game config while editing global
|
||||
// config
|
||||
if (!NativeConfig.isPerGameConfigLoaded() && !setting.global) {
|
||||
return false
|
||||
}
|
||||
return setting.isRuntimeModifiable
|
||||
}
|
||||
|
||||
val needsRuntimeGlobal: Boolean
|
||||
get() = NativeLibrary.isRunning() && !setting.global &&
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
|
||||
companion object {
|
||||
const val TYPE_HEADER = 0
|
||||
const val TYPE_SWITCH = 1
|
||||
|
|
|
@ -19,10 +19,9 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.navArgs
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
||||
|
@ -46,6 +45,9 @@ class SettingsActivity : AppCompatActivity() {
|
|||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
if (!NativeConfig.isPerGameConfigLoaded() && args.game != null) {
|
||||
SettingsFile.loadCustomConfig(args.game!!)
|
||||
}
|
||||
settingsViewModel.game = args.game
|
||||
|
||||
val navHostFragment =
|
||||
|
@ -126,7 +128,6 @@ class SettingsActivity : AppCompatActivity() {
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
// TODO: Load custom settings contextually
|
||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||
DirectoryInitialization.start()
|
||||
}
|
||||
|
@ -134,24 +135,35 @@ class SettingsActivity : AppCompatActivity() {
|
|||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
NativeConfig.saveSettings()
|
||||
Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||
if (isFinishing) {
|
||||
NativeLibrary.applySettings()
|
||||
if (args.game == null) {
|
||||
NativeConfig.saveGlobalConfig()
|
||||
} else if (NativeConfig.isPerGameConfigLoaded()) {
|
||||
NativeLibrary.logSettings()
|
||||
NativeConfig.savePerGameConfig()
|
||||
NativeConfig.unloadPerGameConfig()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
settingsViewModel.clear()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
fun onSettingsReset() {
|
||||
// Delete settings file because the user may have changed values that do not exist in the UI
|
||||
NativeConfig.unloadConfig()
|
||||
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
|
||||
if (!settingsFile.delete()) {
|
||||
throw IOException("Failed to delete $settingsFile")
|
||||
if (args.game == null) {
|
||||
NativeConfig.unloadGlobalConfig()
|
||||
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
|
||||
if (!settingsFile.delete()) {
|
||||
throw IOException("Failed to delete $settingsFile")
|
||||
}
|
||||
NativeConfig.initializeGlobalConfig()
|
||||
} else {
|
||||
NativeConfig.unloadPerGameConfig()
|
||||
val settingsFile = SettingsFile.getCustomSettingsFile(args.game!!)
|
||||
if (!settingsFile.delete()) {
|
||||
throw IOException("Failed to delete $settingsFile")
|
||||
}
|
||||
}
|
||||
NativeConfig.initializeConfig()
|
||||
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
|
|
|
@ -196,6 +196,12 @@ class SettingsAdapter(
|
|||
return true
|
||||
}
|
||||
|
||||
fun onClearClick(item: SettingsItem, position: Int) {
|
||||
item.setting.global = true
|
||||
notifyItemChanged(position)
|
||||
settingsViewModel.setShouldReloadSettingsList(true)
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
|
||||
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
|
||||
return oldItem.setting.key == newItem.setting.key
|
||||
|
|
|
@ -66,7 +66,13 @@ class SettingsFragment : Fragment() {
|
|||
args.menuTag
|
||||
)
|
||||
|
||||
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
|
||||
binding.toolbarSettingsLayout.title = if (args.menuTag == Settings.MenuTag.SECTION_ROOT &&
|
||||
args.game != null
|
||||
) {
|
||||
args.game!!.title
|
||||
} else {
|
||||
getString(args.menuTag.titleId)
|
||||
}
|
||||
binding.listSettings.apply {
|
||||
adapter = settingsAdapter
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.content.SharedPreferences
|
|||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||
|
@ -31,9 +32,17 @@ class SettingsFragmentPresenter(
|
|||
private val preferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
// Extension for populating settings list based on paired settings
|
||||
// Extension for altering settings list based on each setting's properties
|
||||
fun ArrayList<SettingsItem>.add(key: String) {
|
||||
val item = SettingsItem.settingsItems[key]!!
|
||||
if (settingsViewModel.game != null && !item.setting.isSwitchable) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!NativeConfig.isPerGameConfigLoaded() && !NativeLibrary.isRunning()) {
|
||||
item.setting.global = true
|
||||
}
|
||||
|
||||
val pairedSettingKey = item.setting.pairedSettingKey
|
||||
if (pairedSettingKey.isNotEmpty()) {
|
||||
val pairedSettingValue = NativeConfig.getBoolean(
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
|||
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
@ -35,6 +36,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
||||
binding.textSettingValue.text = dateFormatter.format(zonedTime)
|
||||
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
setStyle(setting.isEditable, binding)
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
|
||||
setStyle(setting.isEditable, binding)
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
|
|||
binding.textSettingName.alpha = opacity
|
||||
binding.textSettingDescription.alpha = opacity
|
||||
binding.textSettingValue.alpha = opacity
|
||||
binding.buttonClear.isEnabled = isEditable
|
||||
}
|
||||
|
||||
fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
|
||||
|
@ -48,5 +49,6 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
|
|||
val opacity = if (isEditable) 1.0f else 0.5f
|
||||
binding.textSettingName.alpha = opacity
|
||||
binding.textSettingDescription.alpha = opacity
|
||||
binding.buttonClear.isEnabled = isEditable
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|||
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
@ -43,6 +44,17 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
|||
}
|
||||
}
|
||||
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
setStyle(setting.isEditable, binding)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
|||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
@ -30,6 +31,17 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
|||
setting.units
|
||||
)
|
||||
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
setStyle(setting.isEditable, binding)
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
|
|||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
|||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
@ -29,7 +30,18 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
|||
binding.switchWidget.setOnCheckedChangeListener(null)
|
||||
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
|
||||
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
|
||||
adapter.onBooleanClick(item, binding.switchWidget.isChecked)
|
||||
adapter.onBooleanClick(item, binding.switchWidget.isChecked, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
setStyle(setting.isEditable, binding)
|
||||
|
|
|
@ -3,15 +3,27 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.utils
|
||||
|
||||
import android.net.Uri
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import java.io.*
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
/**
|
||||
* Contains static methods for interacting with .ini files in which settings are stored.
|
||||
*/
|
||||
object SettingsFile {
|
||||
const val FILE_NAME_CONFIG = "config"
|
||||
const val FILE_NAME_CONFIG = "config.ini"
|
||||
|
||||
fun getSettingsFile(fileName: String): File =
|
||||
File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
|
||||
File(DirectoryInitialization.userDirectory + "/config/" + fileName)
|
||||
|
||||
fun getCustomSettingsFile(game: Game): File =
|
||||
File(DirectoryInitialization.userDirectory + "/config/custom/" + game.settingsName + ".ini")
|
||||
|
||||
fun loadCustomConfig(game: Game) {
|
||||
val fileName = FileUtil.getFilename(Uri.parse(game.path))
|
||||
NativeConfig.initializePerGameConfig(game.programId, fileName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
|
|||
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||
|
@ -127,6 +128,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
return
|
||||
}
|
||||
|
||||
if (args.custom) {
|
||||
SettingsFile.loadCustomConfig(args.game!!)
|
||||
NativeConfig.unloadPerGameConfig()
|
||||
} else {
|
||||
NativeConfig.reloadGlobalConfig()
|
||||
}
|
||||
|
||||
// Install the selected driver asynchronously as the game starts
|
||||
driverViewModel.onLaunchGame()
|
||||
|
||||
// So this fragment doesn't restart on configuration changes; i.e. rotation.
|
||||
retainInstance = true
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
@ -217,6 +228,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
true
|
||||
}
|
||||
|
||||
R.id.menu_settings_per_game -> {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
args.game,
|
||||
Settings.MenuTag.SECTION_ROOT
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_overlay_controls -> {
|
||||
showOverlayOptions()
|
||||
true
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.yuzu.yuzu_emu.R
|
|||
import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
|
||||
import org.yuzu.yuzu_emu.model.GameDir
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
|
||||
|
||||
class GameFolderPropertiesDialogFragment : DialogFragment() {
|
||||
|
@ -49,6 +50,11 @@ class GameFolderPropertiesDialogFragment : DialogFragment() {
|
|||
.show()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
NativeConfig.saveGlobalConfig()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putBoolean(DEEP_SCAN, deepScan)
|
||||
|
|
|
@ -304,6 +304,11 @@ class SetupFragment : Fragment() {
|
|||
setInsets()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
NativeConfig.saveGlobalConfig()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
if (_binding != null) {
|
||||
|
|
|
@ -168,6 +168,7 @@ class GamesViewModel : ViewModel() {
|
|||
fun onCloseGameFoldersFragment() =
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
NativeConfig.saveGlobalConfig()
|
||||
getGameDirs(true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,8 +68,4 @@ class SettingsViewModel : ViewModel() {
|
|||
fun setAdapterItemChanged(value: Int) {
|
||||
_adapterItemChanged.value = value
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
game = null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,12 +28,9 @@ import androidx.navigation.ui.setupWithNavController
|
|||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
@ -258,13 +255,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
NativeConfig.saveSettings()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
EmulationActivity.stopForegroundService(this)
|
||||
super.onDestroy()
|
||||
|
@ -677,7 +667,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
}
|
||||
|
||||
// Clear existing user data
|
||||
NativeConfig.unloadConfig()
|
||||
NativeConfig.unloadGlobalConfig()
|
||||
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
|
||||
|
||||
// Copy archive to internal storage
|
||||
|
@ -696,7 +686,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
|
||||
// Reinitialize relevant data
|
||||
NativeLibrary.initializeSystem(true)
|
||||
NativeConfig.initializeConfig()
|
||||
NativeConfig.initializeGlobalConfig()
|
||||
gamesViewModel.reloadGames(false)
|
||||
|
||||
return@newInstance getString(R.string.user_data_import_success)
|
||||
|
|
|
@ -16,7 +16,7 @@ object DirectoryInitialization {
|
|||
if (!areDirectoriesReady) {
|
||||
initializeInternalStorage()
|
||||
NativeLibrary.initializeSystem(false)
|
||||
NativeConfig.initializeConfig()
|
||||
NativeConfig.initializeGlobalConfig()
|
||||
areDirectoriesReady = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,30 +7,54 @@ import org.yuzu.yuzu_emu.model.GameDir
|
|||
|
||||
object NativeConfig {
|
||||
/**
|
||||
* Creates a Config object and opens the emulation config.
|
||||
* Loads global config.
|
||||
*/
|
||||
@Synchronized
|
||||
external fun initializeConfig()
|
||||
external fun initializeGlobalConfig()
|
||||
|
||||
/**
|
||||
* Destroys the stored config object. This automatically saves the existing config.
|
||||
* Destroys the stored global config object. This does not save the existing config.
|
||||
*/
|
||||
@Synchronized
|
||||
external fun unloadConfig()
|
||||
external fun unloadGlobalConfig()
|
||||
|
||||
/**
|
||||
* Reads values saved to the config file and saves them.
|
||||
* Reads values in the global config file and saves them.
|
||||
*/
|
||||
@Synchronized
|
||||
external fun reloadSettings()
|
||||
external fun reloadGlobalConfig()
|
||||
|
||||
/**
|
||||
* Saves settings values in memory to disk.
|
||||
* Saves global settings values in memory to disk.
|
||||
*/
|
||||
@Synchronized
|
||||
external fun saveSettings()
|
||||
external fun saveGlobalConfig()
|
||||
|
||||
external fun getBoolean(key: String, getDefault: Boolean): Boolean
|
||||
/**
|
||||
* Creates per-game config for the specified parameters. Must be unloaded once per-game config
|
||||
* is closed with [unloadPerGameConfig]. All switchable values that [NativeConfig] gets/sets
|
||||
* will follow the per-game config until the global config is reloaded.
|
||||
*
|
||||
* @param programId String representation of the u64 programId
|
||||
* @param fileName Filename of the game, including its extension
|
||||
*/
|
||||
@Synchronized
|
||||
external fun initializePerGameConfig(programId: String, fileName: String)
|
||||
|
||||
@Synchronized
|
||||
external fun isPerGameConfigLoaded(): Boolean
|
||||
|
||||
/**
|
||||
* Saves per-game settings values in memory to disk.
|
||||
*/
|
||||
@Synchronized
|
||||
external fun savePerGameConfig()
|
||||
|
||||
/**
|
||||
* Destroys the stored per-game config object. This does not save the config.
|
||||
*/
|
||||
@Synchronized
|
||||
external fun unloadPerGameConfig()
|
||||
|
||||
@Synchronized
|
||||
external fun getBoolean(key: String, needsGlobal: Boolean): Boolean
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include <common/fs/fs_util.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include "android_config.h"
|
||||
|
@ -12,17 +13,19 @@
|
|||
#include "frontend_common/config.h"
|
||||
#include "jni/android_common/android_common.h"
|
||||
#include "jni/id_cache.h"
|
||||
#include "native.h"
|
||||
|
||||
std::unique_ptr<AndroidConfig> config;
|
||||
std::unique_ptr<AndroidConfig> global_config;
|
||||
std::unique_ptr<AndroidConfig> per_game_config;
|
||||
|
||||
template <typename T>
|
||||
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
||||
auto key = GetJString(env, jkey);
|
||||
auto basicSetting = Settings::values.linkage.by_key[key];
|
||||
auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
|
||||
if (basicSetting != 0) {
|
||||
return static_cast<Settings::Setting<T>*>(basicSetting);
|
||||
}
|
||||
auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
|
||||
if (basicAndroidSetting != 0) {
|
||||
return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
|
||||
}
|
||||
|
@ -32,20 +35,43 @@ Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
|||
|
||||
extern "C" {
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeConfig(JNIEnv* env, jobject obj) {
|
||||
config = std::make_unique<AndroidConfig>();
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
|
||||
global_config = std::make_unique<AndroidConfig>();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadConfig(JNIEnv* env, jobject obj) {
|
||||
config.reset();
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||
global_config.reset();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadSettings(JNIEnv* env, jobject obj) {
|
||||
config->AndroidConfig::ReloadAllValues();
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||
global_config->AndroidConfig::ReloadAllValues();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveSettings(JNIEnv* env, jobject obj) {
|
||||
config->AndroidConfig::SaveAllValues();
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
|
||||
global_config->AndroidConfig::SaveAllValues();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId,
|
||||
jstring jfileName) {
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
auto file_name = GetJString(env, jfileName);
|
||||
const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
|
||||
per_game_config =
|
||||
std::make_unique<AndroidConfig>(config_file_name, Config::ConfigType::PerGameConfig);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_isPerGameConfigLoaded(JNIEnv* env,
|
||||
jobject obj) {
|
||||
return per_game_config != nullptr;
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_savePerGameConfig(JNIEnv* env, jobject obj) {
|
||||
per_game_config->AndroidConfig::SaveAllValues();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadPerGameConfig(JNIEnv* env, jobject obj) {
|
||||
per_game_config.reset();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
|
||||
|
|
|
@ -62,6 +62,16 @@
|
|||
android:textSize="13sp"
|
||||
tools:text="1x" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_clear"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/clear"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -10,41 +10,62 @@
|
|||
android:minHeight="72dp"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_toStartOf="@+id/switch_widget"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_setting_name"
|
||||
style="@style/TextAppearance.Material3.HeadlineMedium"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="17sp"
|
||||
app:lineHeight="28dp"
|
||||
tools:text="@string/frame_limit_enable" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_setting_description"
|
||||
style="@style/TextAppearance.Material3.BodySmall"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:layout_weight="1">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_setting_name"
|
||||
style="@style/TextAppearance.Material3.HeadlineMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="17sp"
|
||||
app:lineHeight="28dp"
|
||||
tools:text="@string/frame_limit_enable" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_setting_description"
|
||||
style="@style/TextAppearance.Material3.BodySmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/frame_limit_enable_description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_clear"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/frame_limit_enable_description" />
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/clear"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -11,6 +11,11 @@
|
|||
android:icon="@drawable/ic_settings"
|
||||
android:title="@string/preferences_settings" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_settings_per_game"
|
||||
android:icon="@drawable/ic_settings_outline"
|
||||
android:title="@string/per_game_settings" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_overlay_controls"
|
||||
android:icon="@drawable/ic_controller"
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
<argument
|
||||
android:name="custom"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
</fragment>
|
||||
|
||||
<activity
|
||||
|
|
|
@ -77,6 +77,10 @@
|
|||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
<argument
|
||||
android:name="custom"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
</activity>
|
||||
|
||||
<action
|
||||
|
|
Reference in New Issue