mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-12-22 16:30:57 +01:00
android: Expose interface for getting settings from native code
Completely removes code related to parsing the settings file on the java side. Now all settings are accessed via NativeConfig.kt and config.cpp has been modified to be closer to the core counterpart. Since the core currently uses QSettings, we can't remove reliance from Wini yet. This also includes simplifications to each settings interface to get closer to native code and prepare for per-game settings.
This commit is contained in:
parent
3d5ecc1f08
commit
6c8f2b355a
49 changed files with 866 additions and 966 deletions
|
@ -219,10 +219,6 @@ object NativeLibrary {
|
|||
|
||||
external fun reloadSettings()
|
||||
|
||||
external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String?
|
||||
|
||||
external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?)
|
||||
|
||||
external fun initGameIni(gameID: String?)
|
||||
|
||||
/**
|
||||
|
@ -413,14 +409,17 @@ object NativeLibrary {
|
|||
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
|
||||
)
|
||||
}
|
||||
|
||||
CoreError.ErrorSavestate -> {
|
||||
title = emulationActivity.getString(R.string.save_load_error)
|
||||
message = details
|
||||
}
|
||||
|
||||
CoreError.ErrorUnknown -> {
|
||||
title = emulationActivity.getString(R.string.fatal_error)
|
||||
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||
}
|
||||
|
||||
else -> {
|
||||
return true
|
||||
}
|
||||
|
@ -454,6 +453,7 @@ object NativeLibrary {
|
|||
captionId = R.string.loader_error_video_core
|
||||
descriptionId = R.string.loader_error_video_core_description
|
||||
}
|
||||
|
||||
else -> {
|
||||
captionId = R.string.loader_error_encrypted
|
||||
descriptionId = R.string.loader_error_encrypted_roms_description
|
||||
|
|
|
@ -28,7 +28,6 @@ import android.view.Surface
|
|||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
@ -42,7 +41,6 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
|||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
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.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
|
@ -72,8 +70,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
override fun onDestroy() {
|
||||
stopForegroundService(this)
|
||||
super.onDestroy()
|
||||
|
@ -82,8 +78,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
ThemeHelper.setTheme(this)
|
||||
|
||||
settingsViewModel.settings.loadSettings()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
|
|
|
@ -4,5 +4,7 @@
|
|||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
interface AbstractBooleanSetting : AbstractSetting {
|
||||
var boolean: Boolean
|
||||
val boolean: Boolean
|
||||
|
||||
fun setBoolean(value: Boolean)
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
interface AbstractByteSetting : AbstractSetting {
|
||||
val byte: Byte
|
||||
|
||||
class SettingsViewModel : ViewModel() {
|
||||
val settings = Settings()
|
||||
fun setByte(value: Byte)
|
||||
}
|
|
@ -4,5 +4,7 @@
|
|||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
interface AbstractFloatSetting : AbstractSetting {
|
||||
var float: Float
|
||||
val float: Float
|
||||
|
||||
fun setFloat(value: Float)
|
||||
}
|
||||
|
|
|
@ -4,5 +4,7 @@
|
|||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
interface AbstractIntSetting : AbstractSetting {
|
||||
var int: Int
|
||||
val int: Int
|
||||
|
||||
fun setInt(value: Int)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
interface AbstractLongSetting : AbstractSetting {
|
||||
val long: Long
|
||||
|
||||
fun setLong(value: Long)
|
||||
}
|
|
@ -3,10 +3,17 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
interface AbstractSetting {
|
||||
val key: String?
|
||||
val section: String?
|
||||
val isRuntimeEditable: Boolean
|
||||
val valueAsString: String
|
||||
val category: Settings.Category
|
||||
val defaultValue: Any
|
||||
val valueAsString: String
|
||||
get() = ""
|
||||
|
||||
val isRuntimeModifiable: Boolean
|
||||
get() = NativeConfig.getIsRuntimeModifiable(key!!)
|
||||
|
||||
fun reset() = run { }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
interface AbstractShortSetting : AbstractSetting {
|
||||
val short: Short
|
||||
|
||||
fun setShort(value: Short)
|
||||
}
|
|
@ -4,5 +4,7 @@
|
|||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
interface AbstractStringSetting : AbstractSetting {
|
||||
var string: String
|
||||
val string: String
|
||||
|
||||
fun setString(value: String)
|
||||
}
|
||||
|
|
|
@ -3,41 +3,34 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class BooleanSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Boolean
|
||||
override val category: Settings.Category
|
||||
) : AbstractBooleanSetting {
|
||||
CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
|
||||
FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
|
||||
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
|
||||
PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
|
||||
CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
|
||||
FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
|
||||
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
|
||||
RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
|
||||
USE_DOCKED_MODE("use_docked_mode", Settings.Category.System),
|
||||
RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
|
||||
RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
|
||||
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
|
||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer),
|
||||
RENDERER_DEBUG("debug", Settings.Category.Renderer),
|
||||
PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
|
||||
|
||||
override var boolean: Boolean = defaultValue
|
||||
override val boolean: Boolean
|
||||
get() = NativeConfig.getBoolean(key, false)
|
||||
|
||||
override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
|
||||
|
||||
override val defaultValue: Boolean by lazy { NativeConfig.getBoolean(key, true) }
|
||||
|
||||
override val valueAsString: String
|
||||
get() = boolean.toString()
|
||||
get() = if (boolean) "1" else "0"
|
||||
|
||||
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 = listOf(
|
||||
PICTURE_IN_PICTURE,
|
||||
USE_CUSTOM_RTC
|
||||
)
|
||||
|
||||
fun from(key: String): BooleanSetting? =
|
||||
BooleanSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
|
||||
}
|
||||
override fun reset() = NativeConfig.setBoolean(key, defaultValue)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class ByteSetting(
|
||||
override val key: String,
|
||||
override val category: Settings.Category
|
||||
) : AbstractByteSetting {
|
||||
AUDIO_VOLUME("volume", Settings.Category.Audio);
|
||||
|
||||
override val byte: Byte
|
||||
get() = NativeConfig.getByte(key, false)
|
||||
|
||||
override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
|
||||
|
||||
override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
|
||||
|
||||
override val valueAsString: String
|
||||
get() = byte.toString()
|
||||
|
||||
override fun reset() = NativeConfig.setByte(key, defaultValue)
|
||||
}
|
|
@ -3,34 +3,24 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class FloatSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Float
|
||||
override val category: Settings.Category
|
||||
) : AbstractFloatSetting {
|
||||
// No float settings currently exist
|
||||
EMPTY_SETTING("", "", 0f);
|
||||
EMPTY_SETTING("", Settings.Category.UiGeneral);
|
||||
|
||||
override var float: Float = defaultValue
|
||||
override val float: Float
|
||||
get() = NativeConfig.getFloat(key, false)
|
||||
|
||||
override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
|
||||
|
||||
override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
|
||||
|
||||
override val valueAsString: String
|
||||
get() = float.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<FloatSetting>()
|
||||
|
||||
fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue }
|
||||
}
|
||||
override fun reset() = NativeConfig.setFloat(key, defaultValue)
|
||||
}
|
||||
|
|
|
@ -3,139 +3,34 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class IntSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Int
|
||||
override val category: Settings.Category
|
||||
) : AbstractIntSetting {
|
||||
RENDERER_USE_SPEED_LIMIT(
|
||||
"use_speed_limit",
|
||||
Settings.SECTION_RENDERER,
|
||||
1
|
||||
),
|
||||
USE_DOCKED_MODE(
|
||||
"use_docked_mode",
|
||||
Settings.SECTION_SYSTEM,
|
||||
0
|
||||
),
|
||||
RENDERER_USE_DISK_SHADER_CACHE(
|
||||
"use_disk_shader_cache",
|
||||
Settings.SECTION_RENDERER,
|
||||
1
|
||||
),
|
||||
RENDERER_FORCE_MAX_CLOCK(
|
||||
"force_max_clock",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_ASYNCHRONOUS_SHADERS(
|
||||
"use_asynchronous_shaders",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_REACTIVE_FLUSHING(
|
||||
"use_reactive_flushing",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_DEBUG(
|
||||
"debug",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_SPEED_LIMIT(
|
||||
"speed_limit",
|
||||
Settings.SECTION_RENDERER,
|
||||
100
|
||||
),
|
||||
CPU_ACCURACY(
|
||||
"cpu_accuracy",
|
||||
Settings.SECTION_CPU,
|
||||
0
|
||||
),
|
||||
REGION_INDEX(
|
||||
"region_index",
|
||||
Settings.SECTION_SYSTEM,
|
||||
-1
|
||||
),
|
||||
LANGUAGE_INDEX(
|
||||
"language_index",
|
||||
Settings.SECTION_SYSTEM,
|
||||
1
|
||||
),
|
||||
RENDERER_BACKEND(
|
||||
"backend",
|
||||
Settings.SECTION_RENDERER,
|
||||
1
|
||||
),
|
||||
RENDERER_ACCURACY(
|
||||
"gpu_accuracy",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_RESOLUTION(
|
||||
"resolution_setup",
|
||||
Settings.SECTION_RENDERER,
|
||||
2
|
||||
),
|
||||
RENDERER_VSYNC(
|
||||
"use_vsync",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_SCALING_FILTER(
|
||||
"scaling_filter",
|
||||
Settings.SECTION_RENDERER,
|
||||
1
|
||||
),
|
||||
RENDERER_ANTI_ALIASING(
|
||||
"anti_aliasing",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_SCREEN_LAYOUT(
|
||||
"screen_layout",
|
||||
Settings.SECTION_RENDERER,
|
||||
Settings.LayoutOption_MobileLandscape
|
||||
),
|
||||
RENDERER_ASPECT_RATIO(
|
||||
"aspect_ratio",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
AUDIO_VOLUME(
|
||||
"volume",
|
||||
Settings.SECTION_AUDIO,
|
||||
100
|
||||
);
|
||||
CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
|
||||
REGION_INDEX("region_index", Settings.Category.System),
|
||||
LANGUAGE_INDEX("language_index", Settings.Category.System),
|
||||
RENDERER_BACKEND("backend", Settings.Category.Renderer),
|
||||
RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer),
|
||||
RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
|
||||
RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
|
||||
RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
|
||||
RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
|
||||
RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
|
||||
RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
|
||||
AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
|
||||
|
||||
override var int: Int = defaultValue
|
||||
override val int: Int
|
||||
get() = NativeConfig.getInt(key, false)
|
||||
|
||||
override fun setInt(value: Int) = NativeConfig.setInt(key, value)
|
||||
|
||||
override val defaultValue: Int by lazy { NativeConfig.getInt(key, true) }
|
||||
|
||||
override val valueAsString: String
|
||||
get() = int.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 = listOf(
|
||||
RENDERER_USE_DISK_SHADER_CACHE,
|
||||
RENDERER_ASYNCHRONOUS_SHADERS,
|
||||
RENDERER_DEBUG,
|
||||
RENDERER_BACKEND,
|
||||
RENDERER_RESOLUTION,
|
||||
RENDERER_VSYNC
|
||||
)
|
||||
|
||||
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
|
||||
}
|
||||
override fun reset() = NativeConfig.setInt(key, defaultValue)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class LongSetting(
|
||||
override val key: String,
|
||||
override val category: Settings.Category
|
||||
) : AbstractLongSetting {
|
||||
CUSTOM_RTC("custom_rtc", Settings.Category.System);
|
||||
|
||||
override val long: Long
|
||||
get() = NativeConfig.getLong(key, false)
|
||||
|
||||
override fun setLong(value: Long) = NativeConfig.setLong(key, value)
|
||||
|
||||
override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
|
||||
|
||||
override val valueAsString: String
|
||||
get() = long.toString()
|
||||
|
||||
override fun reset() = NativeConfig.setLong(key, defaultValue)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_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,195 +4,151 @@
|
|||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import android.text.TextUtils
|
||||
import java.util.*
|
||||
import android.widget.Toast
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
|
||||
class Settings {
|
||||
private var gameId: String? = null
|
||||
object Settings {
|
||||
private val context get() = YuzuApplication.appContext
|
||||
|
||||
var isLoaded = false
|
||||
|
||||
/**
|
||||
* A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
|
||||
* when getting a key not already in the map
|
||||
*/
|
||||
class SettingsSectionMap : HashMap<String, SettingSection?>() {
|
||||
override operator fun get(key: String): SettingSection? {
|
||||
if (!super.containsKey(key)) {
|
||||
val section = SettingSection(key)
|
||||
super.put(key, section)
|
||||
return section
|
||||
}
|
||||
return super.get(key)
|
||||
}
|
||||
}
|
||||
|
||||
var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
|
||||
|
||||
fun getSection(sectionName: String): SettingSection? {
|
||||
return sections[sectionName]
|
||||
}
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = sections.isEmpty()
|
||||
|
||||
fun loadSettings(view: SettingsActivityView? = null) {
|
||||
sections = SettingsSectionMap()
|
||||
loadYuzuSettings(view)
|
||||
if (!TextUtils.isEmpty(gameId)) {
|
||||
loadCustomGameSettings(gameId!!, view)
|
||||
}
|
||||
isLoaded = true
|
||||
}
|
||||
|
||||
private fun loadYuzuSettings(view: SettingsActivityView?) {
|
||||
for ((fileName) in configFileSectionsMap) {
|
||||
sections.putAll(SettingsFile.readFile(fileName, view))
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
|
||||
// Custom game settings
|
||||
mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
|
||||
}
|
||||
|
||||
private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
|
||||
for ((key, updatedSection) in updatedSections) {
|
||||
if (sections.containsKey(key)) {
|
||||
val originalSection = sections[key]
|
||||
originalSection!!.mergeSection(updatedSection!!)
|
||||
} else {
|
||||
sections[key] = updatedSection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadSettings(gameId: String, view: SettingsActivityView) {
|
||||
this.gameId = gameId
|
||||
loadSettings(view)
|
||||
}
|
||||
|
||||
fun saveSettings(view: SettingsActivityView) {
|
||||
fun saveSettings(gameId: String = "") {
|
||||
if (TextUtils.isEmpty(gameId)) {
|
||||
view.showToastMessage(
|
||||
YuzuApplication.appContext.getString(R.string.ini_saved),
|
||||
false
|
||||
)
|
||||
|
||||
for ((fileName, sectionNames) in configFileSectionsMap) {
|
||||
val iniSections = TreeMap<String, SettingSection>()
|
||||
for (section in sectionNames) {
|
||||
iniSections[section] = sections[section]!!
|
||||
}
|
||||
|
||||
SettingsFile.saveFile(fileName, iniSections, view)
|
||||
}
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.ini_saved),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
|
||||
} else {
|
||||
// Custom game settings
|
||||
view.showToastMessage(
|
||||
YuzuApplication.appContext.getString(R.string.gameid_saved, gameId),
|
||||
false
|
||||
)
|
||||
|
||||
SettingsFile.saveCustomGameSettings(gameId, sections)
|
||||
// TODO: Save custom game settings
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.gameid_saved, gameId),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SECTION_GENERAL = "General"
|
||||
const val SECTION_SYSTEM = "System"
|
||||
const val SECTION_RENDERER = "Renderer"
|
||||
const val SECTION_AUDIO = "Audio"
|
||||
const val SECTION_CPU = "Cpu"
|
||||
const val SECTION_THEME = "Theme"
|
||||
const val SECTION_DEBUG = "Debug"
|
||||
|
||||
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
||||
|
||||
const val PREF_OVERLAY_VERSION = "OverlayVersion"
|
||||
const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
|
||||
const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
|
||||
const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
|
||||
val overlayLayoutPrefs = listOf(
|
||||
PREF_LANDSCAPE_OVERLAY_VERSION,
|
||||
PREF_PORTRAIT_OVERLAY_VERSION,
|
||||
PREF_FOLDABLE_OVERLAY_VERSION
|
||||
)
|
||||
|
||||
const val PREF_CONTROL_SCALE = "controlScale"
|
||||
const val PREF_CONTROL_OPACITY = "controlOpacity"
|
||||
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
|
||||
const val PREF_BUTTON_A = "buttonToggle0"
|
||||
const val PREF_BUTTON_B = "buttonToggle1"
|
||||
const val PREF_BUTTON_X = "buttonToggle2"
|
||||
const val PREF_BUTTON_Y = "buttonToggle3"
|
||||
const val PREF_BUTTON_L = "buttonToggle4"
|
||||
const val PREF_BUTTON_R = "buttonToggle5"
|
||||
const val PREF_BUTTON_ZL = "buttonToggle6"
|
||||
const val PREF_BUTTON_ZR = "buttonToggle7"
|
||||
const val PREF_BUTTON_PLUS = "buttonToggle8"
|
||||
const val PREF_BUTTON_MINUS = "buttonToggle9"
|
||||
const val PREF_BUTTON_DPAD = "buttonToggle10"
|
||||
const val PREF_STICK_L = "buttonToggle11"
|
||||
const val PREF_STICK_R = "buttonToggle12"
|
||||
const val PREF_BUTTON_STICK_L = "buttonToggle13"
|
||||
const val PREF_BUTTON_STICK_R = "buttonToggle14"
|
||||
const val PREF_BUTTON_HOME = "buttonToggle15"
|
||||
const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
|
||||
|
||||
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
||||
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
||||
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
|
||||
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
|
||||
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
|
||||
|
||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||
const val PREF_THEME = "Theme"
|
||||
const val PREF_THEME_MODE = "ThemeMode"
|
||||
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
|
||||
|
||||
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
||||
|
||||
val overlayPreferences = listOf(
|
||||
PREF_OVERLAY_VERSION,
|
||||
PREF_CONTROL_SCALE,
|
||||
PREF_CONTROL_OPACITY,
|
||||
PREF_TOUCH_ENABLED,
|
||||
PREF_BUTTON_A,
|
||||
PREF_BUTTON_B,
|
||||
PREF_BUTTON_X,
|
||||
PREF_BUTTON_Y,
|
||||
PREF_BUTTON_L,
|
||||
PREF_BUTTON_R,
|
||||
PREF_BUTTON_ZL,
|
||||
PREF_BUTTON_ZR,
|
||||
PREF_BUTTON_PLUS,
|
||||
PREF_BUTTON_MINUS,
|
||||
PREF_BUTTON_DPAD,
|
||||
PREF_STICK_L,
|
||||
PREF_STICK_R,
|
||||
PREF_BUTTON_HOME,
|
||||
PREF_BUTTON_SCREENSHOT,
|
||||
PREF_BUTTON_STICK_L,
|
||||
PREF_BUTTON_STICK_R
|
||||
)
|
||||
|
||||
const val LayoutOption_Unspecified = 0
|
||||
const val LayoutOption_MobilePortrait = 4
|
||||
const val LayoutOption_MobileLandscape = 5
|
||||
|
||||
init {
|
||||
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
|
||||
listOf(
|
||||
SECTION_GENERAL,
|
||||
SECTION_SYSTEM,
|
||||
SECTION_RENDERER,
|
||||
SECTION_AUDIO,
|
||||
SECTION_CPU
|
||||
)
|
||||
}
|
||||
enum class Category {
|
||||
Android,
|
||||
Audio,
|
||||
Core,
|
||||
Cpu,
|
||||
CpuDebug,
|
||||
CpuUnsafe,
|
||||
Renderer,
|
||||
RendererAdvanced,
|
||||
RendererDebug,
|
||||
System,
|
||||
SystemAudio,
|
||||
DataStorage,
|
||||
Debugging,
|
||||
DebuggingGraphics,
|
||||
Miscellaneous,
|
||||
Network,
|
||||
WebService,
|
||||
AddOns,
|
||||
Controls,
|
||||
Ui,
|
||||
UiGeneral,
|
||||
UiLayout,
|
||||
UiGameList,
|
||||
Screenshots,
|
||||
Shortcuts,
|
||||
Multiplayer,
|
||||
Services,
|
||||
Paths,
|
||||
MaxEnum
|
||||
}
|
||||
|
||||
val settingsList = listOf<AbstractSetting>(
|
||||
*BooleanSetting.values(),
|
||||
*ByteSetting.values(),
|
||||
*ShortSetting.values(),
|
||||
*IntSetting.values(),
|
||||
*FloatSetting.values(),
|
||||
*LongSetting.values(),
|
||||
*StringSetting.values()
|
||||
)
|
||||
|
||||
const val SECTION_GENERAL = "General"
|
||||
const val SECTION_SYSTEM = "System"
|
||||
const val SECTION_RENDERER = "Renderer"
|
||||
const val SECTION_AUDIO = "Audio"
|
||||
const val SECTION_CPU = "Cpu"
|
||||
const val SECTION_THEME = "Theme"
|
||||
const val SECTION_DEBUG = "Debug"
|
||||
|
||||
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
||||
|
||||
const val PREF_OVERLAY_VERSION = "OverlayVersion"
|
||||
const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
|
||||
const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
|
||||
const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
|
||||
val overlayLayoutPrefs = listOf(
|
||||
PREF_LANDSCAPE_OVERLAY_VERSION,
|
||||
PREF_PORTRAIT_OVERLAY_VERSION,
|
||||
PREF_FOLDABLE_OVERLAY_VERSION
|
||||
)
|
||||
|
||||
const val PREF_CONTROL_SCALE = "controlScale"
|
||||
const val PREF_CONTROL_OPACITY = "controlOpacity"
|
||||
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
|
||||
const val PREF_BUTTON_A = "buttonToggle0"
|
||||
const val PREF_BUTTON_B = "buttonToggle1"
|
||||
const val PREF_BUTTON_X = "buttonToggle2"
|
||||
const val PREF_BUTTON_Y = "buttonToggle3"
|
||||
const val PREF_BUTTON_L = "buttonToggle4"
|
||||
const val PREF_BUTTON_R = "buttonToggle5"
|
||||
const val PREF_BUTTON_ZL = "buttonToggle6"
|
||||
const val PREF_BUTTON_ZR = "buttonToggle7"
|
||||
const val PREF_BUTTON_PLUS = "buttonToggle8"
|
||||
const val PREF_BUTTON_MINUS = "buttonToggle9"
|
||||
const val PREF_BUTTON_DPAD = "buttonToggle10"
|
||||
const val PREF_STICK_L = "buttonToggle11"
|
||||
const val PREF_STICK_R = "buttonToggle12"
|
||||
const val PREF_BUTTON_STICK_L = "buttonToggle13"
|
||||
const val PREF_BUTTON_STICK_R = "buttonToggle14"
|
||||
const val PREF_BUTTON_HOME = "buttonToggle15"
|
||||
const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
|
||||
|
||||
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
||||
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
||||
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
|
||||
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
|
||||
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
|
||||
|
||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||
const val PREF_THEME = "Theme"
|
||||
const val PREF_THEME_MODE = "ThemeMode"
|
||||
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
|
||||
|
||||
val overlayPreferences = listOf(
|
||||
PREF_OVERLAY_VERSION,
|
||||
PREF_CONTROL_SCALE,
|
||||
PREF_CONTROL_OPACITY,
|
||||
PREF_TOUCH_ENABLED,
|
||||
PREF_BUTTON_A,
|
||||
PREF_BUTTON_B,
|
||||
PREF_BUTTON_X,
|
||||
PREF_BUTTON_Y,
|
||||
PREF_BUTTON_L,
|
||||
PREF_BUTTON_R,
|
||||
PREF_BUTTON_ZL,
|
||||
PREF_BUTTON_ZR,
|
||||
PREF_BUTTON_PLUS,
|
||||
PREF_BUTTON_MINUS,
|
||||
PREF_BUTTON_DPAD,
|
||||
PREF_STICK_L,
|
||||
PREF_STICK_R,
|
||||
PREF_BUTTON_HOME,
|
||||
PREF_BUTTON_SCREENSHOT,
|
||||
PREF_BUTTON_STICK_L,
|
||||
PREF_BUTTON_STICK_R
|
||||
)
|
||||
|
||||
const val LayoutOption_Unspecified = 0
|
||||
const val LayoutOption_MobilePortrait = 4
|
||||
const val LayoutOption_MobileLandscape = 5
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class ShortSetting(
|
||||
override val key: String,
|
||||
override val category: Settings.Category
|
||||
) : AbstractShortSetting {
|
||||
RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
|
||||
|
||||
override val short: Short
|
||||
get() = NativeConfig.getShort(key, false)
|
||||
|
||||
override fun setShort(value: Short) = NativeConfig.setShort(key, value)
|
||||
|
||||
override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
|
||||
|
||||
override val valueAsString: String
|
||||
get() = short.toString()
|
||||
|
||||
override fun reset() = NativeConfig.setShort(key, defaultValue)
|
||||
}
|
|
@ -3,36 +3,24 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class StringSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: String
|
||||
override val category: Settings.Category
|
||||
) : AbstractStringSetting {
|
||||
AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
|
||||
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
|
||||
// No string settings currently exist
|
||||
EMPTY_SETTING("", Settings.Category.UiGeneral);
|
||||
|
||||
override var string: String = defaultValue
|
||||
override val string: String
|
||||
get() = NativeConfig.getString(key, false)
|
||||
|
||||
override fun setString(value: String) = NativeConfig.setString(key, value)
|
||||
|
||||
override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
|
||||
|
||||
override val valueAsString: String
|
||||
get() = string
|
||||
|
||||
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 = listOf(
|
||||
CUSTOM_RTC
|
||||
)
|
||||
|
||||
fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
|
||||
}
|
||||
override fun reset() = NativeConfig.setString(key, defaultValue)
|
||||
}
|
||||
|
|
|
@ -3,29 +3,29 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
||||
|
||||
class DateTimeSetting(
|
||||
setting: AbstractSetting?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val key: String? = null,
|
||||
private val defaultValue: String? = null
|
||||
private val defaultValue: Long? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_DATETIME_SETTING
|
||||
|
||||
val value: String
|
||||
val value: Long
|
||||
get() = if (setting != null) {
|
||||
val setting = setting as AbstractStringSetting
|
||||
setting.string
|
||||
val setting = setting as AbstractLongSetting
|
||||
setting.long
|
||||
} else {
|
||||
defaultValue!!
|
||||
}
|
||||
|
||||
fun setSelectedValue(datetime: String): AbstractStringSetting {
|
||||
val stringSetting = setting as AbstractStringSetting
|
||||
stringSetting.string = datetime
|
||||
return stringSetting
|
||||
fun setSelectedValue(datetime: Long): AbstractLongSetting {
|
||||
val longSetting = setting as AbstractLongSetting
|
||||
longSetting.setLong(datetime)
|
||||
return longSetting
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ abstract class SettingsItem(
|
|||
val isEditable: Boolean
|
||||
get() {
|
||||
if (!NativeLibrary.isRunning()) return true
|
||||
return setting?.isRuntimeEditable ?: false
|
||||
return setting?.isRuntimeModifiable ?: false
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -33,7 +33,7 @@ class SingleChoiceSetting(
|
|||
*/
|
||||
fun setSelectedValue(selection: Int): AbstractIntSetting {
|
||||
val intSetting = setting as AbstractIntSetting
|
||||
intSetting.int = selection
|
||||
intSetting.setInt(selection)
|
||||
return intSetting
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
|
||||
class SliderSetting(
|
||||
|
@ -17,14 +19,16 @@ class SliderSetting(
|
|||
val max: Int,
|
||||
val units: String,
|
||||
val key: String? = null,
|
||||
val defaultValue: Int? = null
|
||||
val defaultValue: Any? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_SLIDER
|
||||
|
||||
val selectedValue: Int
|
||||
val selectedValue: Any
|
||||
get() {
|
||||
val setting = setting ?: return defaultValue!!
|
||||
return when (setting) {
|
||||
is AbstractByteSetting -> setting.byte.toInt()
|
||||
is AbstractShortSetting -> setting.short.toInt()
|
||||
is AbstractIntSetting -> setting.int
|
||||
is AbstractFloatSetting -> setting.float.roundToInt()
|
||||
else -> {
|
||||
|
@ -43,7 +47,7 @@ class SliderSetting(
|
|||
*/
|
||||
fun setSelectedValue(selection: Int): AbstractIntSetting {
|
||||
val intSetting = setting as AbstractIntSetting
|
||||
intSetting.int = selection
|
||||
intSetting.setInt(selection)
|
||||
return intSetting
|
||||
}
|
||||
|
||||
|
@ -56,7 +60,19 @@ class SliderSetting(
|
|||
*/
|
||||
fun setSelectedValue(selection: Float): AbstractFloatSetting {
|
||||
val floatSetting = setting as AbstractFloatSetting
|
||||
floatSetting.float = selection
|
||||
floatSetting.setFloat(selection)
|
||||
return floatSetting
|
||||
}
|
||||
|
||||
fun setSelectedValue(selection: Short): AbstractShortSetting {
|
||||
val shortSetting = setting as AbstractShortSetting
|
||||
shortSetting.setShort(selection)
|
||||
return shortSetting
|
||||
}
|
||||
|
||||
fun setSelectedValue(selection: Byte): AbstractByteSetting {
|
||||
val byteSetting = setting as AbstractByteSetting
|
||||
byteSetting.setByte(selection)
|
||||
return byteSetting
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ class StringSingleChoiceSetting(
|
|||
*/
|
||||
fun setSelectedValue(selection: String): AbstractStringSetting {
|
||||
val stringSetting = setting as AbstractStringSetting
|
||||
stringSetting.string = selection
|
||||
stringSetting.setString(selection)
|
||||
return stringSetting
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,14 +49,14 @@ class SwitchSetting(
|
|||
// Try integer setting
|
||||
try {
|
||||
val setting = setting as AbstractIntSetting
|
||||
setting.int = if (checked) 1 else 0
|
||||
setting.setInt(if (checked) 1 else 0)
|
||||
return setting
|
||||
} catch (_: ClassCastException) {
|
||||
}
|
||||
|
||||
// Try boolean setting
|
||||
val setting = setting as AbstractBooleanSetting
|
||||
setting.boolean = checked
|
||||
setting.setBoolean(checked)
|
||||
return setting
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,7 @@ import com.google.android.material.color.MaterialColors
|
|||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
|
||||
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.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
|
||||
|
@ -35,10 +30,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||
|
||||
private lateinit var binding: ActivitySettingsBinding
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
override val settings: Settings get() = settingsViewModel.settings
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
ThemeHelper.setTheme(this)
|
||||
|
||||
|
@ -171,14 +162,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||
fragment?.loadSettingsList()
|
||||
}
|
||||
|
||||
override fun showToastMessage(message: String, is_long: Boolean) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
message,
|
||||
if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun onSettingChanged() {
|
||||
presenter.onSettingChanged()
|
||||
}
|
||||
|
@ -187,19 +170,18 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||
// Prevents saving to a non-existent settings file
|
||||
presenter.onSettingsReset()
|
||||
|
||||
// Reset the static memory representation of each setting
|
||||
BooleanSetting.clear()
|
||||
FloatSetting.clear()
|
||||
IntSetting.clear()
|
||||
StringSetting.clear()
|
||||
|
||||
// Delete settings file because the user may have changed values that do not exist in the UI
|
||||
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
|
||||
if (!settingsFile.delete()) {
|
||||
throw IOException("Failed to delete $settingsFile")
|
||||
}
|
||||
Settings.settingsList.forEach { it.reset() }
|
||||
|
||||
showToastMessage(getString(R.string.settings_reset), true)
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
getString(R.string.settings_reset),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
@ -14,8 +13,6 @@ import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
|||
import org.yuzu.yuzu_emu.utils.Log
|
||||
|
||||
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
|
||||
val settings: Settings get() = activityView.settings
|
||||
|
||||
private var shouldSave = false
|
||||
private lateinit var menuTag: String
|
||||
private lateinit var gameId: String
|
||||
|
@ -33,13 +30,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
|||
}
|
||||
|
||||
private fun loadSettingsUI() {
|
||||
if (!settings.isLoaded) {
|
||||
if (!TextUtils.isEmpty(gameId)) {
|
||||
settings.loadSettings(gameId, activityView)
|
||||
} else {
|
||||
settings.loadSettings(activityView)
|
||||
}
|
||||
}
|
||||
// TODO: Load custom settings contextually
|
||||
activityView.showSettingsFragment(menuTag, false, gameId)
|
||||
activityView.onSettingsFileLoaded()
|
||||
}
|
||||
|
@ -67,9 +58,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
|||
fun onStop(finishing: Boolean) {
|
||||
if (finishing && shouldSave) {
|
||||
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||
settings.saveSettings(activityView)
|
||||
Settings.saveSettings()
|
||||
NativeLibrary.reloadSettings()
|
||||
}
|
||||
NativeLibrary.reloadSettings()
|
||||
}
|
||||
|
||||
fun onSettingChanged() {
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
||||
/**
|
||||
* Abstraction for the Activity that manages SettingsFragments.
|
||||
*/
|
||||
|
@ -17,15 +15,6 @@ interface SettingsActivityView {
|
|||
*/
|
||||
fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
|
||||
|
||||
/**
|
||||
* Called by a contained Fragment to get access to the Setting HashMap
|
||||
* loaded from disk, so that each Fragment doesn't need to perform its own
|
||||
* read operation.
|
||||
*
|
||||
* @return A HashMap of Settings.
|
||||
*/
|
||||
val settings: Settings
|
||||
|
||||
/**
|
||||
* Called when a load operation completes.
|
||||
*/
|
||||
|
@ -36,14 +25,6 @@ interface SettingsActivityView {
|
|||
*/
|
||||
fun onSettingsFileNotFound()
|
||||
|
||||
/**
|
||||
* Display a popup text message on screen.
|
||||
*
|
||||
* @param message The contents of the onscreen message.
|
||||
* @param is_long Whether this should be a long Toast or short one.
|
||||
*/
|
||||
fun showToastMessage(message: String, is_long: Boolean)
|
||||
|
||||
/**
|
||||
* End the activity.
|
||||
*/
|
||||
|
|
|
@ -24,12 +24,10 @@ import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
|||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
|
||||
|
||||
|
@ -115,8 +113,7 @@ class SettingsAdapter(
|
|||
}
|
||||
|
||||
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
|
||||
val setting = item.setChecked(checked)
|
||||
fragmentView.putSetting(setting)
|
||||
item.setChecked(checked)
|
||||
fragmentView.onSettingChanged()
|
||||
}
|
||||
|
||||
|
@ -150,7 +147,7 @@ class SettingsAdapter(
|
|||
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
|
||||
clickedItem = item
|
||||
clickedPosition = position
|
||||
val storedTime = java.lang.Long.decode(item.value) * 1000
|
||||
val storedTime = item.value * 1000
|
||||
|
||||
// Helper to extract hour and minute from epoch time
|
||||
val calendar: Calendar = Calendar.getInstance()
|
||||
|
@ -183,13 +180,11 @@ class SettingsAdapter(
|
|||
var epochTime: Long = datePicker.selection!! / 1000
|
||||
epochTime += timePicker.hour.toLong() * 60 * 60
|
||||
epochTime += timePicker.minute.toLong() * 60
|
||||
val rtcString = epochTime.toString()
|
||||
if (item.value != rtcString) {
|
||||
if (item.value != epochTime) {
|
||||
fragmentView.onSettingChanged()
|
||||
notifyItemChanged(clickedPosition)
|
||||
item.setSelectedValue(epochTime)
|
||||
}
|
||||
notifyItemChanged(clickedPosition)
|
||||
val setting = item.setSelectedValue(rtcString)
|
||||
fragmentView.putSetting(setting)
|
||||
clickedItem = null
|
||||
}
|
||||
datePicker.show(
|
||||
|
@ -201,7 +196,7 @@ class SettingsAdapter(
|
|||
fun onSliderClick(item: SliderSetting, position: Int) {
|
||||
clickedItem = item
|
||||
clickedPosition = position
|
||||
sliderProgress = item.selectedValue
|
||||
sliderProgress = item.selectedValue as Int
|
||||
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val sliderBinding = DialogSliderBinding.inflate(inflater)
|
||||
|
@ -249,8 +244,7 @@ class SettingsAdapter(
|
|||
}
|
||||
|
||||
// Get the backing Setting, which may be null (if for example it was missing from the file)
|
||||
val setting = scSetting.setSelectedValue(value)
|
||||
fragmentView.putSetting(setting)
|
||||
scSetting.setSelectedValue(value)
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
|
@ -258,8 +252,7 @@ class SettingsAdapter(
|
|||
val scSetting = clickedItem as StringSingleChoiceSetting
|
||||
val value = scSetting.getValueAt(which)
|
||||
if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
|
||||
val setting = scSetting.setSelectedValue(value!!)
|
||||
fragmentView.putSetting(setting)
|
||||
scSetting.setSelectedValue(value!!)
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
|
@ -268,13 +261,25 @@ class SettingsAdapter(
|
|||
if (sliderSetting.selectedValue != sliderProgress) {
|
||||
fragmentView.onSettingChanged()
|
||||
}
|
||||
if (sliderSetting.setting is FloatSetting) {
|
||||
val value = sliderProgress.toFloat()
|
||||
val setting = sliderSetting.setSelectedValue(value)
|
||||
fragmentView.putSetting(setting)
|
||||
} else {
|
||||
val setting = sliderSetting.setSelectedValue(sliderProgress)
|
||||
fragmentView.putSetting(setting)
|
||||
when (sliderSetting.setting) {
|
||||
is ByteSetting -> {
|
||||
val value = sliderProgress.toByte()
|
||||
sliderSetting.setSelectedValue(value)
|
||||
}
|
||||
|
||||
is ShortSetting -> {
|
||||
val value = sliderProgress.toShort()
|
||||
sliderSetting.setSelectedValue(value)
|
||||
}
|
||||
|
||||
is FloatSetting -> {
|
||||
val value = sliderProgress.toFloat()
|
||||
sliderSetting.setSelectedValue(value)
|
||||
}
|
||||
|
||||
else -> {
|
||||
sliderSetting.setSelectedValue(sliderProgress)
|
||||
}
|
||||
}
|
||||
closeDialog()
|
||||
}
|
||||
|
@ -286,13 +291,8 @@ class SettingsAdapter(
|
|||
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
|
||||
when (setting) {
|
||||
is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
|
||||
is AbstractFloatSetting -> setting.float = setting.defaultValue as Float
|
||||
is AbstractIntSetting -> setting.int = setting.defaultValue as Int
|
||||
is AbstractStringSetting -> setting.string = setting.defaultValue as String
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
setting.reset()
|
||||
notifyItemChanged(position)
|
||||
fragmentView.onSettingChanged()
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
|
||||
class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||
|
@ -89,14 +88,6 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
|||
)
|
||||
}
|
||||
|
||||
override fun showToastMessage(message: String?, is_long: Boolean) {
|
||||
activityView!!.showToastMessage(message!!, is_long)
|
||||
}
|
||||
|
||||
override fun putSetting(setting: AbstractSetting) {
|
||||
fragmentPresenter.putSetting(setting)
|
||||
}
|
||||
|
||||
override fun onSettingChanged() {
|
||||
activityView!!.onSettingChanged()
|
||||
}
|
||||
|
|
|
@ -6,16 +6,18 @@ package org.yuzu.yuzu_emu.features.settings.ui
|
|||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
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.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
|
||||
|
@ -27,7 +29,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
private var settingsList: ArrayList<SettingsItem>? = null
|
||||
|
||||
private val settingsActivity get() = fragmentView.activityView as SettingsActivity
|
||||
private val settings get() = fragmentView.activityView!!.settings
|
||||
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
||||
|
@ -41,17 +42,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
loadSettingsList()
|
||||
}
|
||||
|
||||
fun putSetting(setting: AbstractSetting) {
|
||||
if (setting.section == null || setting.key == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val section = settings.getSection(setting.section!!)!!
|
||||
if (section.getSetting(setting.key!!) == null) {
|
||||
section.putSetting(setting)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadSettingsList() {
|
||||
if (!TextUtils.isEmpty(gameId)) {
|
||||
settingsActivity.setToolbarTitle("Game Settings: $gameId")
|
||||
|
@ -69,7 +59,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
Settings.SECTION_THEME -> addThemeSettings(sl)
|
||||
Settings.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
else -> {
|
||||
fragmentView.showToastMessage("Unimplemented menu", false)
|
||||
val context = YuzuApplication.appContext
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.unimplemented_menu),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -135,23 +130,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
sl.apply {
|
||||
add(
|
||||
SwitchSetting(
|
||||
IntSetting.RENDERER_USE_SPEED_LIMIT,
|
||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||
R.string.frame_limit_enable,
|
||||
R.string.frame_limit_enable_description,
|
||||
IntSetting.RENDERER_USE_SPEED_LIMIT.key,
|
||||
IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
|
||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT.key,
|
||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SliderSetting(
|
||||
IntSetting.RENDERER_SPEED_LIMIT,
|
||||
ShortSetting.RENDERER_SPEED_LIMIT,
|
||||
R.string.frame_limit_slider,
|
||||
R.string.frame_limit_slider_description,
|
||||
1,
|
||||
200,
|
||||
"%",
|
||||
IntSetting.RENDERER_SPEED_LIMIT.key,
|
||||
IntSetting.RENDERER_SPEED_LIMIT.defaultValue
|
||||
ShortSetting.RENDERER_SPEED_LIMIT.key,
|
||||
ShortSetting.RENDERER_SPEED_LIMIT.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
|
@ -182,11 +177,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
sl.apply {
|
||||
add(
|
||||
SwitchSetting(
|
||||
IntSetting.USE_DOCKED_MODE,
|
||||
BooleanSetting.USE_DOCKED_MODE,
|
||||
R.string.use_docked_mode,
|
||||
R.string.use_docked_mode_description,
|
||||
IntSetting.USE_DOCKED_MODE.key,
|
||||
IntSetting.USE_DOCKED_MODE.defaultValue
|
||||
BooleanSetting.USE_DOCKED_MODE.key,
|
||||
BooleanSetting.USE_DOCKED_MODE.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
|
@ -222,11 +217,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
)
|
||||
add(
|
||||
DateTimeSetting(
|
||||
StringSetting.CUSTOM_RTC,
|
||||
LongSetting.CUSTOM_RTC,
|
||||
R.string.set_custom_rtc,
|
||||
0,
|
||||
StringSetting.CUSTOM_RTC.key,
|
||||
StringSetting.CUSTOM_RTC.defaultValue
|
||||
LongSetting.CUSTOM_RTC.key,
|
||||
LongSetting.CUSTOM_RTC.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -314,38 +309,38 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
|
||||
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
|
||||
R.string.use_disk_shader_cache,
|
||||
R.string.use_disk_shader_cache_description,
|
||||
IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
|
||||
IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
|
||||
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
|
||||
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
IntSetting.RENDERER_FORCE_MAX_CLOCK,
|
||||
BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
|
||||
R.string.renderer_force_max_clock,
|
||||
R.string.renderer_force_max_clock_description,
|
||||
IntSetting.RENDERER_FORCE_MAX_CLOCK.key,
|
||||
IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
|
||||
BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key,
|
||||
BooleanSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
||||
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
||||
R.string.renderer_asynchronous_shaders,
|
||||
R.string.renderer_asynchronous_shaders_description,
|
||||
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
|
||||
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
|
||||
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
|
||||
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
IntSetting.RENDERER_REACTIVE_FLUSHING,
|
||||
BooleanSetting.RENDERER_REACTIVE_FLUSHING,
|
||||
R.string.renderer_reactive_flushing,
|
||||
R.string.renderer_reactive_flushing_description,
|
||||
IntSetting.RENDERER_REACTIVE_FLUSHING.key,
|
||||
IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
|
||||
BooleanSetting.RENDERER_REACTIVE_FLUSHING.key,
|
||||
BooleanSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -355,26 +350,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
|
||||
sl.apply {
|
||||
add(
|
||||
StringSingleChoiceSetting(
|
||||
StringSetting.AUDIO_OUTPUT_ENGINE,
|
||||
SingleChoiceSetting(
|
||||
IntSetting.AUDIO_OUTPUT_ENGINE,
|
||||
R.string.audio_output_engine,
|
||||
0,
|
||||
settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
|
||||
settingsActivity.resources.getStringArray(R.array.outputEngineValues),
|
||||
StringSetting.AUDIO_OUTPUT_ENGINE.key,
|
||||
StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
|
||||
R.array.outputEngineEntries,
|
||||
R.array.outputEngineValues,
|
||||
IntSetting.AUDIO_OUTPUT_ENGINE.key,
|
||||
IntSetting.AUDIO_OUTPUT_ENGINE.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SliderSetting(
|
||||
IntSetting.AUDIO_VOLUME,
|
||||
ByteSetting.AUDIO_VOLUME,
|
||||
R.string.audio_volume,
|
||||
R.string.audio_volume_description,
|
||||
0,
|
||||
100,
|
||||
"%",
|
||||
IntSetting.AUDIO_VOLUME.key,
|
||||
IntSetting.AUDIO_VOLUME.defaultValue
|
||||
ByteSetting.AUDIO_VOLUME.key,
|
||||
ByteSetting.AUDIO_VOLUME.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -384,19 +379,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
|
||||
sl.apply {
|
||||
val theme: AbstractIntSetting = object : AbstractIntSetting {
|
||||
override var int: Int
|
||||
override val int: Int
|
||||
get() = preferences.getInt(Settings.PREF_THEME, 0)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_THEME, value)
|
||||
.apply()
|
||||
settingsActivity.recreate()
|
||||
}
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_THEME, value)
|
||||
.apply()
|
||||
settingsActivity.recreate()
|
||||
}
|
||||
|
||||
override val key: String? = null
|
||||
override val section: String? = null
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String
|
||||
get() = preferences.getInt(Settings.PREF_THEME, 0).toString()
|
||||
override val category = Settings.Category.UiGeneral
|
||||
override val isRuntimeModifiable: Boolean = false
|
||||
override val defaultValue: Any = 0
|
||||
}
|
||||
|
||||
|
@ -423,19 +418,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
}
|
||||
|
||||
val themeMode: AbstractIntSetting = object : AbstractIntSetting {
|
||||
override var int: Int
|
||||
override val int: Int
|
||||
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_THEME_MODE, value)
|
||||
.apply()
|
||||
ThemeHelper.setThemeMode(settingsActivity)
|
||||
}
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_THEME_MODE, value)
|
||||
.apply()
|
||||
ThemeHelper.setThemeMode(settingsActivity)
|
||||
}
|
||||
|
||||
override val key: String? = null
|
||||
override val section: String? = null
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String
|
||||
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString()
|
||||
override val category = Settings.Category.UiGeneral
|
||||
override val isRuntimeModifiable: Boolean = false
|
||||
override val defaultValue: Any = -1
|
||||
}
|
||||
|
||||
|
@ -450,20 +445,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
)
|
||||
|
||||
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
|
||||
override var boolean: Boolean
|
||||
override val boolean: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
|
||||
.apply()
|
||||
settingsActivity.recreate()
|
||||
}
|
||||
|
||||
override fun setBoolean(value: Boolean) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
|
||||
.apply()
|
||||
settingsActivity.recreate()
|
||||
}
|
||||
|
||||
override val key: String? = null
|
||||
override val section: String? = null
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String
|
||||
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
|
||||
.toString()
|
||||
override val category = Settings.Category.UiGeneral
|
||||
override val isRuntimeModifiable: Boolean = false
|
||||
override val defaultValue: Any = false
|
||||
}
|
||||
|
||||
|
@ -494,11 +488,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
IntSetting.RENDERER_DEBUG,
|
||||
BooleanSetting.RENDERER_DEBUG,
|
||||
R.string.renderer_debug,
|
||||
R.string.renderer_debug_description,
|
||||
IntSetting.RENDERER_DEBUG.key,
|
||||
IntSetting.RENDERER_DEBUG.defaultValue
|
||||
BooleanSetting.RENDERER_DEBUG.key,
|
||||
BooleanSetting.RENDERER_DEBUG.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -514,17 +508,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
)
|
||||
|
||||
val fastmem = object : AbstractBooleanSetting {
|
||||
override var boolean: Boolean
|
||||
override val boolean: Boolean
|
||||
get() =
|
||||
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
|
||||
set(value) {
|
||||
BooleanSetting.FASTMEM.boolean = value
|
||||
BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
|
||||
}
|
||||
|
||||
override fun setBoolean(value: Boolean) {
|
||||
BooleanSetting.FASTMEM.setBoolean(value)
|
||||
BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
|
||||
}
|
||||
|
||||
override val key: String? = null
|
||||
override val section: String = Settings.SECTION_CPU
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String = ""
|
||||
override val category = Settings.Category.Cpu
|
||||
override val isRuntimeModifiable: Boolean = false
|
||||
override val defaultValue: Any = true
|
||||
}
|
||||
add(
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
|
||||
/**
|
||||
|
@ -36,21 +35,6 @@ interface SettingsFragmentView {
|
|||
*/
|
||||
fun loadSubMenu(menuKey: String)
|
||||
|
||||
/**
|
||||
* Tell the Fragment to tell the containing activity to display a toast message.
|
||||
*
|
||||
* @param message Text to be shown in the Toast
|
||||
* @param is_long Whether this should be a long Toast or short one.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -29,7 +29,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||
}
|
||||
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
val epochTime = setting.value.toLong()
|
||||
val epochTime = setting.value
|
||||
val instant = Instant.ofEpochMilli(epochTime * 1000)
|
||||
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
|
||||
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
||||
|
|
|
@ -3,18 +3,15 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.utils
|
||||
|
||||
import android.widget.Toast
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import org.ini4j.Wini
|
||||
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.*
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
|
||||
import org.yuzu.yuzu_emu.utils.BiMap
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
/**
|
||||
* Contains static methods for interacting with .ini files in which settings are stored.
|
||||
|
@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log
|
|||
object SettingsFile {
|
||||
const val FILE_NAME_CONFIG = "config"
|
||||
|
||||
private var sectionsMap = BiMap<String?, String?>()
|
||||
|
||||
/**
|
||||
* Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
|
||||
* effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
|
||||
* failed.
|
||||
*
|
||||
* @param ini The ini file to load the settings from
|
||||
* @param isCustomGame
|
||||
* @param view The current view.
|
||||
* @return An Observable that emits a HashMap of the file's contents, then completes.
|
||||
*/
|
||||
private fun readFile(
|
||||
ini: File?,
|
||||
isCustomGame: Boolean,
|
||||
view: SettingsActivityView? = null
|
||||
): HashMap<String, SettingSection?> {
|
||||
val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
|
||||
var reader: BufferedReader? = null
|
||||
try {
|
||||
reader = BufferedReader(FileReader(ini))
|
||||
var current: SettingSection? = null
|
||||
var line: String?
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
if (line!!.startsWith("[") && line!!.endsWith("]")) {
|
||||
current = sectionFromLine(line!!, isCustomGame)
|
||||
sections[current.name] = current
|
||||
} else if (current != null) {
|
||||
val setting = settingFromLine(line!!)
|
||||
if (setting != null) {
|
||||
current.putSetting(setting)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
Log.error("[SettingsFile] File not found: " + e.message)
|
||||
view?.onSettingsFileNotFound()
|
||||
} catch (e: IOException) {
|
||||
Log.error("[SettingsFile] Error reading from: " + e.message)
|
||||
view?.onSettingsFileNotFound()
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close()
|
||||
} catch (e: IOException) {
|
||||
Log.error("[SettingsFile] Error closing: " + e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
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(getSettingsFile(fileName), false)
|
||||
|
||||
/**
|
||||
* Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
|
||||
* effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
|
||||
* failed.
|
||||
*
|
||||
* @param gameId the id of the game to load it's settings.
|
||||
* @param view The current view.
|
||||
*/
|
||||
fun readCustomGameSettings(
|
||||
gameId: String,
|
||||
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
|
||||
* telling why it failed.
|
||||
*
|
||||
* @param fileName The target filename without a path or extension.
|
||||
* @param sections The HashMap containing the Settings we want to serialize.
|
||||
* @param view The current view.
|
||||
*/
|
||||
fun saveFile(
|
||||
fileName: String,
|
||||
sections: TreeMap<String, SettingSection>,
|
||||
view: SettingsActivityView
|
||||
) {
|
||||
fun saveFile(fileName: String) {
|
||||
val ini = getSettingsFile(fileName)
|
||||
try {
|
||||
val writer = Wini(ini)
|
||||
val keySet: Set<String> = sections.keys
|
||||
for (key in keySet) {
|
||||
val section = sections[key]
|
||||
writeSection(writer, section!!)
|
||||
val wini = Wini(ini)
|
||||
for (specificCategory in Settings.Category.values()) {
|
||||
val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
|
||||
for (setting in Settings.settingsList) {
|
||||
if (setting.key!!.isEmpty()) continue
|
||||
|
||||
val settingCategoryHeader =
|
||||
NativeConfig.getConfigHeader(setting.category.ordinal)
|
||||
val iniSetting: String? = wini.get(categoryHeader, setting.key)
|
||||
if (iniSetting != null || settingCategoryHeader == categoryHeader) {
|
||||
wini.put(settingCategoryHeader, setting.key, setting.valueAsString)
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.store()
|
||||
wini.store()
|
||||
} catch (e: IOException) {
|
||||
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
|
||||
view.showToastMessage(
|
||||
YuzuApplication.appContext
|
||||
.getString(R.string.error_saving, fileName, e.message),
|
||||
false
|
||||
)
|
||||
val context = YuzuApplication.appContext
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.error_saving, fileName, e.message),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
|
||||
val sortedSections: Set<String> = TreeSet(sections.keys)
|
||||
for (sectionKey in sortedSections) {
|
||||
val section = sections[sectionKey]
|
||||
val settings = section!!.settings
|
||||
val sortedKeySet: Set<String> = TreeSet(settings.keys)
|
||||
for (settingKey in sortedKeySet) {
|
||||
val setting = settings[settingKey]
|
||||
NativeLibrary.setUserSetting(
|
||||
gameId,
|
||||
mapSectionNameFromIni(
|
||||
section.name
|
||||
),
|
||||
setting!!.key,
|
||||
setting.valueAsString
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapSectionNameFromIni(generalSectionName: String): String? {
|
||||
return if (sectionsMap.getForward(generalSectionName) != null) {
|
||||
sectionsMap.getForward(generalSectionName)
|
||||
} else {
|
||||
generalSectionName
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapSectionNameToIni(generalSectionName: String): String {
|
||||
return if (sectionsMap.getBackward(generalSectionName) != null) {
|
||||
sectionsMap.getBackward(generalSectionName).toString()
|
||||
} else {
|
||||
generalSectionName
|
||||
}
|
||||
}
|
||||
|
||||
fun getSettingsFile(fileName: String): File {
|
||||
return File(
|
||||
DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCustomGameSettingsFile(gameId: String): File {
|
||||
return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
|
||||
}
|
||||
|
||||
private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
|
||||
var sectionName: String = line.substring(1, line.length - 1)
|
||||
if (isCustomGame) {
|
||||
sectionName = mapSectionNameToIni(sectionName)
|
||||
}
|
||||
return SettingSection(sectionName)
|
||||
}
|
||||
|
||||
/**
|
||||
* For a line of text, determines what type of data is being represented, and returns
|
||||
* a Setting object containing this data.
|
||||
*
|
||||
* @param line The line of text being parsed.
|
||||
* @return A typed Setting containing the key/value contained in the line.
|
||||
*/
|
||||
private fun settingFromLine(line: String): AbstractSetting? {
|
||||
val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (splitLine.size != 2) {
|
||||
return null
|
||||
}
|
||||
val key = splitLine[0].trim { it <= ' ' }
|
||||
val value = splitLine[1].trim { it <= ' ' }
|
||||
if (value.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val booleanSetting = BooleanSetting.from(key)
|
||||
if (booleanSetting != null) {
|
||||
booleanSetting.boolean = value.toBoolean()
|
||||
return booleanSetting
|
||||
}
|
||||
|
||||
val intSetting = IntSetting.from(key)
|
||||
if (intSetting != null) {
|
||||
intSetting.int = value.toInt()
|
||||
return intSetting
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
BooleanSetting.values().forEach {
|
||||
if (!keySet.contains(it.key)) {
|
||||
parser.put(header, it.key, it.valueAsString)
|
||||
}
|
||||
}
|
||||
IntSetting.values().forEach {
|
||||
if (!keySet.contains(it.key)) {
|
||||
parser.put(header, it.key, it.valueAsString)
|
||||
}
|
||||
}
|
||||
StringSetting.values().forEach {
|
||||
if (!keySet.contains(it.key)) {
|
||||
parser.put(header, it.key, it.valueAsString)
|
||||
}
|
||||
}
|
||||
}
|
||||
fun getSettingsFile(fileName: String): File =
|
||||
File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
|
|||
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
|
||||
|
@ -54,7 +53,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
|
||||
private val homeViewModel: HomeViewModel by viewModels()
|
||||
private val gamesViewModel: GamesViewModel by viewModels()
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
override var themeId: Int = 0
|
||||
|
||||
|
@ -62,8 +60,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
val splashScreen = installSplashScreen()
|
||||
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
|
||||
|
||||
settingsViewModel.settings.loadSettings()
|
||||
|
||||
ThemeHelper.setTheme(this)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
class BiMap<K, V> {
|
||||
private val forward: MutableMap<K, V> = HashMap()
|
||||
private val backward: MutableMap<V, K> = HashMap()
|
||||
|
||||
@Synchronized
|
||||
fun add(key: K, value: V) {
|
||||
forward[key] = value
|
||||
backward[value] = key
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getForward(key: K): V? {
|
||||
return forward[key]
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getBackward(key: V): K? {
|
||||
return backward[key]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
object NativeConfig {
|
||||
external fun getBoolean(key: String, getDefault: Boolean): Boolean
|
||||
external fun setBoolean(key: String, value: Boolean)
|
||||
|
||||
external fun getByte(key: String, getDefault: Boolean): Byte
|
||||
external fun setByte(key: String, value: Byte)
|
||||
|
||||
external fun getShort(key: String, getDefault: Boolean): Short
|
||||
external fun setShort(key: String, value: Short)
|
||||
|
||||
external fun getInt(key: String, getDefault: Boolean): Int
|
||||
external fun setInt(key: String, value: Int)
|
||||
|
||||
external fun getFloat(key: String, getDefault: Boolean): Float
|
||||
external fun setFloat(key: String, value: Float)
|
||||
|
||||
external fun getLong(key: String, getDefault: Boolean): Long
|
||||
external fun setLong(key: String, value: Long)
|
||||
|
||||
external fun getString(key: String, getDefault: Boolean): String
|
||||
external fun setString(key: String, value: String)
|
||||
|
||||
external fun getIsRuntimeModifiable(key: String): Boolean
|
||||
|
||||
external fun getConfigHeader(category: Int): String
|
||||
}
|
|
@ -14,6 +14,8 @@ add_library(yuzu-android SHARED
|
|||
id_cache.cpp
|
||||
id_cache.h
|
||||
native.cpp
|
||||
native_config.cpp
|
||||
uisettings.cpp
|
||||
)
|
||||
|
||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||
|
|
|
@ -16,18 +16,20 @@
|
|||
#include "input_common/main.h"
|
||||
#include "jni/config.h"
|
||||
#include "jni/default_ini.h"
|
||||
#include "uisettings.h"
|
||||
|
||||
namespace FS = Common::FS;
|
||||
|
||||
Config::Config(std::optional<std::filesystem::path> config_path)
|
||||
: config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")},
|
||||
config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} {
|
||||
Reload();
|
||||
Config::Config(const std::string& config_name, ConfigType config_type)
|
||||
: type(config_type), global{config_type == ConfigType::GlobalConfig} {
|
||||
Initialize(config_name);
|
||||
}
|
||||
|
||||
Config::~Config() = default;
|
||||
|
||||
bool Config::LoadINI(const std::string& default_contents, bool retry) {
|
||||
void(FS::CreateParentDir(config_loc));
|
||||
config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
|
||||
const auto config_loc_str = FS::PathToUTF8String(config_loc);
|
||||
if (config->ParseError() < 0) {
|
||||
if (retry) {
|
||||
|
@ -301,9 +303,28 @@ void Config::ReadValues() {
|
|||
|
||||
// Network
|
||||
ReadSetting("Network", Settings::values.network_interface);
|
||||
|
||||
// Android
|
||||
ReadSetting("Android", AndroidSettings::values.picture_in_picture);
|
||||
ReadSetting("Android", AndroidSettings::values.screen_layout);
|
||||
}
|
||||
|
||||
void Config::Reload() {
|
||||
void Config::Initialize(const std::string& config_name) {
|
||||
const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
|
||||
const auto config_file = fmt::format("{}.ini", config_name);
|
||||
|
||||
switch (type) {
|
||||
case ConfigType::GlobalConfig:
|
||||
config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
|
||||
break;
|
||||
case ConfigType::PerGameConfig:
|
||||
config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
|
||||
break;
|
||||
case ConfigType::InputProfile:
|
||||
config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
|
||||
LoadINI(DefaultINI::android_config_file);
|
||||
return;
|
||||
}
|
||||
LoadINI(DefaultINI::android_config_file);
|
||||
ReadValues();
|
||||
}
|
||||
|
|
|
@ -13,25 +13,35 @@
|
|||
class INIReader;
|
||||
|
||||
class Config {
|
||||
std::filesystem::path config_loc;
|
||||
std::unique_ptr<INIReader> config;
|
||||
|
||||
bool LoadINI(const std::string& default_contents = "", bool retry = true);
|
||||
void ReadValues();
|
||||
|
||||
public:
|
||||
explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt);
|
||||
enum class ConfigType {
|
||||
GlobalConfig,
|
||||
PerGameConfig,
|
||||
InputProfile,
|
||||
};
|
||||
|
||||
explicit Config(const std::string& config_name = "config",
|
||||
ConfigType config_type = ConfigType::GlobalConfig);
|
||||
~Config();
|
||||
|
||||
void Reload();
|
||||
void Initialize(const std::string& config_name);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Applies a value read from the sdl2_config to a Setting.
|
||||
* Applies a value read from the config to a Setting.
|
||||
*
|
||||
* @param group The name of the INI group
|
||||
* @param setting The yuzu setting to modify
|
||||
*/
|
||||
template <typename Type, bool ranged>
|
||||
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
|
||||
|
||||
void ReadValues();
|
||||
|
||||
const ConfigType type;
|
||||
std::unique_ptr<INIReader> config;
|
||||
std::string config_loc;
|
||||
const bool global;
|
||||
};
|
||||
|
|
|
@ -824,34 +824,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl
|
|||
Config{};
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id, jstring j_section,
|
||||
jstring j_key) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
std::string_view section = env->GetStringUTFChars(j_section, 0);
|
||||
std::string_view key = env->GetStringUTFChars(j_key, 0);
|
||||
|
||||
env->ReleaseStringUTFChars(j_game_id, game_id.data());
|
||||
env->ReleaseStringUTFChars(j_section, section.data());
|
||||
env->ReleaseStringUTFChars(j_key, key.data());
|
||||
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id, jstring j_section,
|
||||
jstring j_key, jstring j_value) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
std::string_view section = env->GetStringUTFChars(j_section, 0);
|
||||
std::string_view key = env->GetStringUTFChars(j_key, 0);
|
||||
std::string_view value = env->GetStringUTFChars(j_value, 0);
|
||||
|
||||
env->ReleaseStringUTFChars(j_game_id, game_id.data());
|
||||
env->ReleaseStringUTFChars(j_section, section.data());
|
||||
env->ReleaseStringUTFChars(j_key, key.data());
|
||||
env->ReleaseStringUTFChars(j_value, value.data());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
|
|
224
src/android/app/src/main/jni/native_config.cpp
Normal file
224
src/android/app/src/main/jni/native_config.cpp
Normal file
|
@ -0,0 +1,224 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "jni/android_common/android_common.h"
|
||||
#include "jni/config.h"
|
||||
#include "uisettings.h"
|
||||
|
||||
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);
|
||||
}
|
||||
if (basicAndroidSetting != 0) {
|
||||
return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
|
||||
}
|
||||
LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
|
||||
jstring jkey, jboolean getDefault) {
|
||||
auto setting = getSetting<bool>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return false;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
|
||||
if (static_cast<bool>(getDefault)) {
|
||||
return setting->GetDefault();
|
||||
}
|
||||
|
||||
return setting->GetValue();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jboolean value) {
|
||||
auto setting = getSetting<bool>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
setting->SetValue(static_cast<bool>(value));
|
||||
}
|
||||
|
||||
jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jboolean getDefault) {
|
||||
auto setting = getSetting<u8>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
|
||||
if (static_cast<bool>(getDefault)) {
|
||||
return setting->GetDefault();
|
||||
}
|
||||
|
||||
return setting->GetValue();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jbyte value) {
|
||||
auto setting = getSetting<u8>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
setting->SetValue(value);
|
||||
}
|
||||
|
||||
jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jboolean getDefault) {
|
||||
auto setting = getSetting<u16>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
|
||||
if (static_cast<bool>(getDefault)) {
|
||||
return setting->GetDefault();
|
||||
}
|
||||
|
||||
return setting->GetValue();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jshort value) {
|
||||
auto setting = getSetting<u16>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
setting->SetValue(value);
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jboolean getDefault) {
|
||||
auto setting = getSetting<int>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
|
||||
if (static_cast<bool>(getDefault)) {
|
||||
return setting->GetDefault();
|
||||
}
|
||||
|
||||
return setting->GetValue();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jint value) {
|
||||
auto setting = getSetting<int>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
setting->SetValue(value);
|
||||
}
|
||||
|
||||
jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jboolean getDefault) {
|
||||
auto setting = getSetting<float>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
|
||||
if (static_cast<bool>(getDefault)) {
|
||||
return setting->GetDefault();
|
||||
}
|
||||
|
||||
return setting->GetValue();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jfloat value) {
|
||||
auto setting = getSetting<float>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
setting->SetValue(value);
|
||||
}
|
||||
|
||||
jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jboolean getDefault) {
|
||||
auto setting = getSetting<long>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
|
||||
if (static_cast<bool>(getDefault)) {
|
||||
return setting->GetDefault();
|
||||
}
|
||||
|
||||
return setting->GetValue();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jlong value) {
|
||||
auto setting = getSetting<long>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return;
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
setting->SetValue(value);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jboolean getDefault) {
|
||||
auto setting = getSetting<std::string>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return ToJString(env, "");
|
||||
}
|
||||
setting->SetGlobal(true);
|
||||
|
||||
if (static_cast<bool>(getDefault)) {
|
||||
return ToJString(env, setting->GetDefault());
|
||||
}
|
||||
|
||||
return ToJString(env, setting->GetValue());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jstring value) {
|
||||
auto setting = getSetting<std::string>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
setting->SetGlobal(true);
|
||||
setting->SetValue(GetJString(env, value));
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj,
|
||||
jstring jkey) {
|
||||
auto key = GetJString(env, jkey);
|
||||
auto setting = Settings::values.linkage.by_key[key];
|
||||
if (setting != 0) {
|
||||
return setting->RuntimeModfiable();
|
||||
}
|
||||
LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
|
||||
return true;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj,
|
||||
jint jcategory) {
|
||||
auto category = static_cast<Settings::Category>(jcategory);
|
||||
return ToJString(env, Settings::TranslateCategory(category));
|
||||
}
|
||||
|
||||
} // extern "C"
|
10
src/android/app/src/main/jni/uisettings.cpp
Normal file
10
src/android/app/src/main/jni/uisettings.cpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "uisettings.h"
|
||||
|
||||
namespace AndroidSettings {
|
||||
|
||||
Values values;
|
||||
|
||||
} // namespace AndroidSettings
|
29
src/android/app/src/main/jni/uisettings.h
Normal file
29
src/android/app/src/main/jni/uisettings.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common/settings_common.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings_setting.h"
|
||||
|
||||
namespace AndroidSettings {
|
||||
|
||||
struct Values {
|
||||
Settings::Linkage linkage;
|
||||
|
||||
// Android
|
||||
Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
|
||||
Settings::Category::Android};
|
||||
Settings::Setting<s32> screen_layout{linkage,
|
||||
5,
|
||||
"screen_layout",
|
||||
Settings::Category::Android,
|
||||
Settings::Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
} // namespace AndroidSettings
|
|
@ -243,10 +243,10 @@
|
|||
<item>@string/cubeb</item>
|
||||
<item>@string/string_null</item>
|
||||
</string-array>
|
||||
<string-array name="outputEngineValues">
|
||||
<item>auto</item>
|
||||
<item>cubeb</item>
|
||||
<item>null</item>
|
||||
</string-array>
|
||||
<integer-array name="outputEngineValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -200,6 +200,7 @@
|
|||
<string name="ini_saved">Saved settings</string>
|
||||
<string name="gameid_saved">Saved settings for %1$s</string>
|
||||
<string name="error_saving">Error saving %1$s.ini: %2$s</string>
|
||||
<string name="unimplemented_menu">Unimplemented Menu</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
|
||||
<string name="reset_to_default">Reset to default</string>
|
||||
|
|
|
@ -159,6 +159,8 @@ float Volume() {
|
|||
|
||||
const char* TranslateCategory(Category category) {
|
||||
switch (category) {
|
||||
case Category::Android:
|
||||
return "Android";
|
||||
case Category::Audio:
|
||||
return "Audio";
|
||||
case Category::Core:
|
||||
|
|
|
@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ
|
|||
: label{name}, category{category_}, id{linkage.count}, save{save_},
|
||||
runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
|
||||
other_setting{other_setting_} {
|
||||
linkage.by_key.insert({name, this});
|
||||
linkage.by_category[category].push_back(this);
|
||||
linkage.count++;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace Settings {
|
||||
|
||||
enum class Category : u32 {
|
||||
Android,
|
||||
Audio,
|
||||
Core,
|
||||
Cpu,
|
||||
|
@ -68,6 +69,7 @@ public:
|
|||
explicit Linkage(u32 initial_count = 0);
|
||||
~Linkage();
|
||||
std::map<Category, std::vector<BasicSetting*>> by_category{};
|
||||
std::map<std::string, Settings::BasicSetting*> by_key{};
|
||||
std::vector<std::function<void()>> restore_functions{};
|
||||
u32 count;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue