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:
Charles Lombardo 2023-08-16 02:36:56 -04:00
parent 3d5ecc1f08
commit 6c8f2b355a
49 changed files with 866 additions and 966 deletions

View file

@ -219,10 +219,6 @@ object NativeLibrary {
external fun reloadSettings() 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?) external fun initGameIni(gameID: String?)
/** /**
@ -413,14 +409,17 @@ object NativeLibrary {
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
) )
} }
CoreError.ErrorSavestate -> { CoreError.ErrorSavestate -> {
title = emulationActivity.getString(R.string.save_load_error) title = emulationActivity.getString(R.string.save_load_error)
message = details message = details
} }
CoreError.ErrorUnknown -> { CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error) title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message) message = emulationActivity.getString(R.string.fatal_error_message)
} }
else -> { else -> {
return true return true
} }
@ -454,6 +453,7 @@ object NativeLibrary {
captionId = R.string.loader_error_video_core captionId = R.string.loader_error_video_core
descriptionId = R.string.loader_error_video_core_description descriptionId = R.string.loader_error_video_core_description
} }
else -> { else -> {
captionId = R.string.loader_error_encrypted captionId = R.string.loader_error_encrypted
descriptionId = R.string.loader_error_encrypted_roms_description descriptionId = R.string.loader_error_encrypted_roms_description

View file

@ -28,7 +28,6 @@ import android.view.Surface
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat 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.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting 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.Settings
import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.ForegroundService import org.yuzu.yuzu_emu.utils.ForegroundService
@ -72,8 +70,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private val actionMute = "ACTION_EMULATOR_MUTE" private val actionMute = "ACTION_EMULATOR_MUTE"
private val actionUnmute = "ACTION_EMULATOR_UNMUTE" private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
private val settingsViewModel: SettingsViewModel by viewModels()
override fun onDestroy() { override fun onDestroy() {
stopForegroundService(this) stopForegroundService(this)
super.onDestroy() super.onDestroy()
@ -82,8 +78,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this) ThemeHelper.setTheme(this)
settingsViewModel.settings.loadSettings()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityEmulationBinding.inflate(layoutInflater) binding = ActivityEmulationBinding.inflate(layoutInflater)

View file

@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
interface AbstractBooleanSetting : AbstractSetting { interface AbstractBooleanSetting : AbstractSetting {
var boolean: Boolean val boolean: Boolean
fun setBoolean(value: Boolean)
} }

View file

@ -3,8 +3,8 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
import androidx.lifecycle.ViewModel interface AbstractByteSetting : AbstractSetting {
val byte: Byte
class SettingsViewModel : ViewModel() { fun setByte(value: Byte)
val settings = Settings()
} }

View file

@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
interface AbstractFloatSetting : AbstractSetting { interface AbstractFloatSetting : AbstractSetting {
var float: Float val float: Float
fun setFloat(value: Float)
} }

View file

@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
interface AbstractIntSetting : AbstractSetting { interface AbstractIntSetting : AbstractSetting {
var int: Int val int: Int
fun setInt(value: Int)
} }

View file

@ -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)
}

View file

@ -3,10 +3,17 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
interface AbstractSetting { interface AbstractSetting {
val key: String? val key: String?
val section: String? val category: Settings.Category
val isRuntimeEditable: Boolean
val valueAsString: String
val defaultValue: Any val defaultValue: Any
val valueAsString: String
get() = ""
val isRuntimeModifiable: Boolean
get() = NativeConfig.getIsRuntimeModifiable(key!!)
fun reset() = run { }
} }

View file

@ -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)
}

View file

@ -4,5 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
interface AbstractStringSetting : AbstractSetting { interface AbstractStringSetting : AbstractSetting {
var string: String val string: String
fun setString(value: String)
} }

View file

@ -3,41 +3,34 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class BooleanSetting( enum class BooleanSetting(
override val key: String, override val key: String,
override val section: String, override val category: Settings.Category
override val defaultValue: Boolean
) : AbstractBooleanSetting { ) : AbstractBooleanSetting {
CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false), CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true), FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true), FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); 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 override val valueAsString: String
get() = boolean.toString() get() = if (boolean) "1" else "0"
override val isRuntimeEditable: Boolean override fun reset() = NativeConfig.setBoolean(key, defaultValue)
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 }
}
} }

View file

@ -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)
}

View file

@ -3,34 +3,24 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class FloatSetting( enum class FloatSetting(
override val key: String, override val key: String,
override val section: String, override val category: Settings.Category
override val defaultValue: Float
) : AbstractFloatSetting { ) : AbstractFloatSetting {
// No float settings currently exist // 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 override val valueAsString: String
get() = float.toString() get() = float.toString()
override val isRuntimeEditable: Boolean override fun reset() = NativeConfig.setFloat(key, defaultValue)
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 }
}
} }

View file

@ -3,139 +3,34 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class IntSetting( enum class IntSetting(
override val key: String, override val key: String,
override val section: String, override val category: Settings.Category
override val defaultValue: Int
) : AbstractIntSetting { ) : AbstractIntSetting {
RENDERER_USE_SPEED_LIMIT( CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
"use_speed_limit", REGION_INDEX("region_index", Settings.Category.System),
Settings.SECTION_RENDERER, LANGUAGE_INDEX("language_index", Settings.Category.System),
1 RENDERER_BACKEND("backend", Settings.Category.Renderer),
), RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer),
USE_DOCKED_MODE( RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
"use_docked_mode", RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
Settings.SECTION_SYSTEM, RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
0 RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
), RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
RENDERER_USE_DISK_SHADER_CACHE( RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
"use_disk_shader_cache", AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
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
);
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 override val valueAsString: String
get() = int.toString() get() = int.toString()
override val isRuntimeEditable: Boolean override fun reset() = NativeConfig.setInt(key, defaultValue)
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 }
}
} }

View file

@ -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)
}

View file

@ -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)
}
}
}

View file

@ -4,195 +4,151 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
import android.text.TextUtils import android.text.TextUtils
import java.util.* import android.widget.Toast
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
class Settings { object Settings {
private var gameId: String? = null private val context get() = YuzuApplication.appContext
var isLoaded = false fun saveSettings(gameId: String = "") {
/**
* 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) {
if (TextUtils.isEmpty(gameId)) { if (TextUtils.isEmpty(gameId)) {
view.showToastMessage( Toast.makeText(
YuzuApplication.appContext.getString(R.string.ini_saved), context,
false context.getString(R.string.ini_saved),
) Toast.LENGTH_SHORT
).show()
for ((fileName, sectionNames) in configFileSectionsMap) { SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
val iniSections = TreeMap<String, SettingSection>()
for (section in sectionNames) {
iniSections[section] = sections[section]!!
}
SettingsFile.saveFile(fileName, iniSections, view)
}
} else { } else {
// Custom game settings // TODO: Save custom game settings
view.showToastMessage( Toast.makeText(
YuzuApplication.appContext.getString(R.string.gameid_saved, gameId), context,
false context.getString(R.string.gameid_saved, gameId),
) Toast.LENGTH_SHORT
).show()
SettingsFile.saveCustomGameSettings(gameId, sections)
} }
} }
companion object { enum class Category {
const val SECTION_GENERAL = "General" Android,
const val SECTION_SYSTEM = "System" Audio,
const val SECTION_RENDERER = "Renderer" Core,
const val SECTION_AUDIO = "Audio" Cpu,
const val SECTION_CPU = "Cpu" CpuDebug,
const val SECTION_THEME = "Theme" CpuUnsafe,
const val SECTION_DEBUG = "Debug" Renderer,
RendererAdvanced,
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" RendererDebug,
System,
const val PREF_OVERLAY_VERSION = "OverlayVersion" SystemAudio,
const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" DataStorage,
const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" Debugging,
const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" DebuggingGraphics,
val overlayLayoutPrefs = listOf( Miscellaneous,
PREF_LANDSCAPE_OVERLAY_VERSION, Network,
PREF_PORTRAIT_OVERLAY_VERSION, WebService,
PREF_FOLDABLE_OVERLAY_VERSION AddOns,
) Controls,
Ui,
const val PREF_CONTROL_SCALE = "controlScale" UiGeneral,
const val PREF_CONTROL_OPACITY = "controlOpacity" UiLayout,
const val PREF_TOUCH_ENABLED = "isTouchEnabled" UiGameList,
const val PREF_BUTTON_A = "buttonToggle0" Screenshots,
const val PREF_BUTTON_B = "buttonToggle1" Shortcuts,
const val PREF_BUTTON_X = "buttonToggle2" Multiplayer,
const val PREF_BUTTON_Y = "buttonToggle3" Services,
const val PREF_BUTTON_L = "buttonToggle4" Paths,
const val PREF_BUTTON_R = "buttonToggle5" MaxEnum
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
)
}
} }
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
} }

View file

@ -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)
}

View file

@ -3,36 +3,24 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class StringSetting( enum class StringSetting(
override val key: String, override val key: String,
override val section: String, override val category: Settings.Category
override val defaultValue: String
) : AbstractStringSetting { ) : AbstractStringSetting {
AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"), // No string settings currently exist
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); 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 override val valueAsString: String
get() = string get() = string
override val isRuntimeEditable: Boolean override fun reset() = NativeConfig.setString(key, defaultValue)
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 }
}
} }

View file

@ -3,29 +3,29 @@
package org.yuzu.yuzu_emu.features.settings.model.view 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.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
class DateTimeSetting( class DateTimeSetting(
setting: AbstractSetting?, setting: AbstractSetting?,
titleId: Int, titleId: Int,
descriptionId: Int, descriptionId: Int,
val key: String? = null, val key: String? = null,
private val defaultValue: String? = null private val defaultValue: Long? = null
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING override val type = TYPE_DATETIME_SETTING
val value: String val value: Long
get() = if (setting != null) { get() = if (setting != null) {
val setting = setting as AbstractStringSetting val setting = setting as AbstractLongSetting
setting.string setting.long
} else { } else {
defaultValue!! defaultValue!!
} }
fun setSelectedValue(datetime: String): AbstractStringSetting { fun setSelectedValue(datetime: Long): AbstractLongSetting {
val stringSetting = setting as AbstractStringSetting val longSetting = setting as AbstractLongSetting
stringSetting.string = datetime longSetting.setLong(datetime)
return stringSetting return longSetting
} }
} }

View file

@ -23,7 +23,7 @@ abstract class SettingsItem(
val isEditable: Boolean val isEditable: Boolean
get() { get() {
if (!NativeLibrary.isRunning()) return true if (!NativeLibrary.isRunning()) return true
return setting?.isRuntimeEditable ?: false return setting?.isRuntimeModifiable ?: false
} }
companion object { companion object {

View file

@ -33,7 +33,7 @@ class SingleChoiceSetting(
*/ */
fun setSelectedValue(selection: Int): AbstractIntSetting { fun setSelectedValue(selection: Int): AbstractIntSetting {
val intSetting = setting as AbstractIntSetting val intSetting = setting as AbstractIntSetting
intSetting.int = selection intSetting.setInt(selection)
return intSetting return intSetting
} }
} }

View file

@ -3,10 +3,12 @@
package org.yuzu.yuzu_emu.features.settings.model.view package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
import kotlin.math.roundToInt import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting 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.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 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 import org.yuzu.yuzu_emu.utils.Log
class SliderSetting( class SliderSetting(
@ -17,14 +19,16 @@ class SliderSetting(
val max: Int, val max: Int,
val units: String, val units: String,
val key: String? = null, val key: String? = null,
val defaultValue: Int? = null val defaultValue: Any? = null
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER override val type = TYPE_SLIDER
val selectedValue: Int val selectedValue: Any
get() { get() {
val setting = setting ?: return defaultValue!! val setting = setting ?: return defaultValue!!
return when (setting) { return when (setting) {
is AbstractByteSetting -> setting.byte.toInt()
is AbstractShortSetting -> setting.short.toInt()
is AbstractIntSetting -> setting.int is AbstractIntSetting -> setting.int
is AbstractFloatSetting -> setting.float.roundToInt() is AbstractFloatSetting -> setting.float.roundToInt()
else -> { else -> {
@ -43,7 +47,7 @@ class SliderSetting(
*/ */
fun setSelectedValue(selection: Int): AbstractIntSetting { fun setSelectedValue(selection: Int): AbstractIntSetting {
val intSetting = setting as AbstractIntSetting val intSetting = setting as AbstractIntSetting
intSetting.int = selection intSetting.setInt(selection)
return intSetting return intSetting
} }
@ -56,7 +60,19 @@ class SliderSetting(
*/ */
fun setSelectedValue(selection: Float): AbstractFloatSetting { fun setSelectedValue(selection: Float): AbstractFloatSetting {
val floatSetting = setting as AbstractFloatSetting val floatSetting = setting as AbstractFloatSetting
floatSetting.float = selection floatSetting.setFloat(selection)
return floatSetting 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
}
} }

View file

@ -53,7 +53,7 @@ class StringSingleChoiceSetting(
*/ */
fun setSelectedValue(selection: String): AbstractStringSetting { fun setSelectedValue(selection: String): AbstractStringSetting {
val stringSetting = setting as AbstractStringSetting val stringSetting = setting as AbstractStringSetting
stringSetting.string = selection stringSetting.setString(selection)
return stringSetting return stringSetting
} }
} }

View file

@ -49,14 +49,14 @@ class SwitchSetting(
// Try integer setting // Try integer setting
try { try {
val setting = setting as AbstractIntSetting val setting = setting as AbstractIntSetting
setting.int = if (checked) 1 else 0 setting.setInt(if (checked) 1 else 0)
return setting return setting
} catch (_: ClassCastException) { } catch (_: ClassCastException) {
} }
// Try boolean setting // Try boolean setting
val setting = setting as AbstractBooleanSetting val setting = setting as AbstractBooleanSetting
setting.boolean = checked setting.setBoolean(checked)
return setting return setting
} }
} }

View file

@ -21,12 +21,7 @@ import com.google.android.material.color.MaterialColors
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 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.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.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.utils.* import org.yuzu.yuzu_emu.utils.*
@ -35,10 +30,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
private lateinit var binding: ActivitySettingsBinding private lateinit var binding: ActivitySettingsBinding
private val settingsViewModel: SettingsViewModel by viewModels()
override val settings: Settings get() = settingsViewModel.settings
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this) ThemeHelper.setTheme(this)
@ -171,14 +162,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
fragment?.loadSettingsList() 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() { override fun onSettingChanged() {
presenter.onSettingChanged() presenter.onSettingChanged()
} }
@ -187,19 +170,18 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
// Prevents saving to a non-existent settings file // Prevents saving to a non-existent settings file
presenter.onSettingsReset() 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 // Delete settings file because the user may have changed values that do not exist in the UI
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) { if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile") 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() finish()
} }

View file

@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import java.io.File import java.io.File
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.features.settings.model.Settings 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 import org.yuzu.yuzu_emu.utils.Log
class SettingsActivityPresenter(private val activityView: SettingsActivityView) { class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
val settings: Settings get() = activityView.settings
private var shouldSave = false private var shouldSave = false
private lateinit var menuTag: String private lateinit var menuTag: String
private lateinit var gameId: String private lateinit var gameId: String
@ -33,13 +30,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
} }
private fun loadSettingsUI() { private fun loadSettingsUI() {
if (!settings.isLoaded) { // TODO: Load custom settings contextually
if (!TextUtils.isEmpty(gameId)) {
settings.loadSettings(gameId, activityView)
} else {
settings.loadSettings(activityView)
}
}
activityView.showSettingsFragment(menuTag, false, gameId) activityView.showSettingsFragment(menuTag, false, gameId)
activityView.onSettingsFileLoaded() activityView.onSettingsFileLoaded()
} }
@ -67,9 +58,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
fun onStop(finishing: Boolean) { fun onStop(finishing: Boolean) {
if (finishing && shouldSave) { if (finishing && shouldSave) {
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
settings.saveSettings(activityView) Settings.saveSettings()
NativeLibrary.reloadSettings()
} }
NativeLibrary.reloadSettings()
} }
fun onSettingChanged() { fun onSettingChanged() {

View file

@ -3,8 +3,6 @@
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
import org.yuzu.yuzu_emu.features.settings.model.Settings
/** /**
* Abstraction for the Activity that manages SettingsFragments. * Abstraction for the Activity that manages SettingsFragments.
*/ */
@ -17,15 +15,6 @@ interface SettingsActivityView {
*/ */
fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) 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. * Called when a load operation completes.
*/ */
@ -36,14 +25,6 @@ interface SettingsActivityView {
*/ */
fun onSettingsFileNotFound() 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. * End the activity.
*/ */

View file

@ -24,12 +24,10 @@ import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding 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.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.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.model.view.*
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
@ -115,8 +113,7 @@ class SettingsAdapter(
} }
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
val setting = item.setChecked(checked) item.setChecked(checked)
fragmentView.putSetting(setting)
fragmentView.onSettingChanged() fragmentView.onSettingChanged()
} }
@ -150,7 +147,7 @@ class SettingsAdapter(
fun onDateTimeClick(item: DateTimeSetting, position: Int) { fun onDateTimeClick(item: DateTimeSetting, position: Int) {
clickedItem = item clickedItem = item
clickedPosition = position 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 // Helper to extract hour and minute from epoch time
val calendar: Calendar = Calendar.getInstance() val calendar: Calendar = Calendar.getInstance()
@ -183,13 +180,11 @@ class SettingsAdapter(
var epochTime: Long = datePicker.selection!! / 1000 var epochTime: Long = datePicker.selection!! / 1000
epochTime += timePicker.hour.toLong() * 60 * 60 epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60 epochTime += timePicker.minute.toLong() * 60
val rtcString = epochTime.toString() if (item.value != epochTime) {
if (item.value != rtcString) {
fragmentView.onSettingChanged() fragmentView.onSettingChanged()
notifyItemChanged(clickedPosition)
item.setSelectedValue(epochTime)
} }
notifyItemChanged(clickedPosition)
val setting = item.setSelectedValue(rtcString)
fragmentView.putSetting(setting)
clickedItem = null clickedItem = null
} }
datePicker.show( datePicker.show(
@ -201,7 +196,7 @@ class SettingsAdapter(
fun onSliderClick(item: SliderSetting, position: Int) { fun onSliderClick(item: SliderSetting, position: Int) {
clickedItem = item clickedItem = item
clickedPosition = position clickedPosition = position
sliderProgress = item.selectedValue sliderProgress = item.selectedValue as Int
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(context)
val sliderBinding = DialogSliderBinding.inflate(inflater) 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) // Get the backing Setting, which may be null (if for example it was missing from the file)
val setting = scSetting.setSelectedValue(value) scSetting.setSelectedValue(value)
fragmentView.putSetting(setting)
closeDialog() closeDialog()
} }
@ -258,8 +252,7 @@ class SettingsAdapter(
val scSetting = clickedItem as StringSingleChoiceSetting val scSetting = clickedItem as StringSingleChoiceSetting
val value = scSetting.getValueAt(which) val value = scSetting.getValueAt(which)
if (scSetting.selectedValue != value) fragmentView.onSettingChanged() if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
val setting = scSetting.setSelectedValue(value!!) scSetting.setSelectedValue(value!!)
fragmentView.putSetting(setting)
closeDialog() closeDialog()
} }
@ -268,13 +261,25 @@ class SettingsAdapter(
if (sliderSetting.selectedValue != sliderProgress) { if (sliderSetting.selectedValue != sliderProgress) {
fragmentView.onSettingChanged() fragmentView.onSettingChanged()
} }
if (sliderSetting.setting is FloatSetting) { when (sliderSetting.setting) {
val value = sliderProgress.toFloat() is ByteSetting -> {
val setting = sliderSetting.setSelectedValue(value) val value = sliderProgress.toByte()
fragmentView.putSetting(setting) sliderSetting.setSelectedValue(value)
} else { }
val setting = sliderSetting.setSelectedValue(sliderProgress)
fragmentView.putSetting(setting) is ShortSetting -> {
val value = sliderProgress.toShort()
sliderSetting.setSelectedValue(value)
}
is FloatSetting -> {
val value = sliderProgress.toFloat()
sliderSetting.setSelectedValue(value)
}
else -> {
sliderSetting.setSelectedValue(sliderProgress)
}
} }
closeDialog() closeDialog()
} }
@ -286,13 +291,8 @@ class SettingsAdapter(
fun onLongClick(setting: AbstractSetting, position: Int): Boolean { fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
.setMessage(R.string.reset_setting_confirmation) .setMessage(R.string.reset_setting_confirmation)
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int -> .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (setting) { setting.reset()
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
}
notifyItemChanged(position) notifyItemChanged(position)
fragmentView.onSettingChanged() fragmentView.onSettingChanged()
} }

View file

@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.divider.MaterialDividerItemDecoration
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding 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 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
class SettingsFragment : Fragment(), SettingsFragmentView { 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() { override fun onSettingChanged() {
activityView!!.onSettingChanged() activityView!!.onSettingChanged()
} }

View file

@ -6,16 +6,18 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.text.TextUtils import android.text.TextUtils
import android.widget.Toast
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting 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.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.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.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.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.model.view.*
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
@ -27,7 +29,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private var settingsList: ArrayList<SettingsItem>? = null private var settingsList: ArrayList<SettingsItem>? = null
private val settingsActivity get() = fragmentView.activityView as SettingsActivity private val settingsActivity get() = fragmentView.activityView as SettingsActivity
private val settings get() = fragmentView.activityView!!.settings
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
@ -41,17 +42,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
loadSettingsList() 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() { fun loadSettingsList() {
if (!TextUtils.isEmpty(gameId)) { if (!TextUtils.isEmpty(gameId)) {
settingsActivity.setToolbarTitle("Game Settings: $gameId") settingsActivity.setToolbarTitle("Game Settings: $gameId")
@ -69,7 +59,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_THEME -> addThemeSettings(sl) Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl) Settings.SECTION_DEBUG -> addDebugSettings(sl)
else -> { else -> {
fragmentView.showToastMessage("Unimplemented menu", false) val context = YuzuApplication.appContext
Toast.makeText(
context,
context.getString(R.string.unimplemented_menu),
Toast.LENGTH_SHORT
).show()
return return
} }
} }
@ -135,23 +130,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
sl.apply { sl.apply {
add( add(
SwitchSetting( SwitchSetting(
IntSetting.RENDERER_USE_SPEED_LIMIT, BooleanSetting.RENDERER_USE_SPEED_LIMIT,
R.string.frame_limit_enable, R.string.frame_limit_enable,
R.string.frame_limit_enable_description, R.string.frame_limit_enable_description,
IntSetting.RENDERER_USE_SPEED_LIMIT.key, BooleanSetting.RENDERER_USE_SPEED_LIMIT.key,
IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue BooleanSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
) )
) )
add( add(
SliderSetting( SliderSetting(
IntSetting.RENDERER_SPEED_LIMIT, ShortSetting.RENDERER_SPEED_LIMIT,
R.string.frame_limit_slider, R.string.frame_limit_slider,
R.string.frame_limit_slider_description, R.string.frame_limit_slider_description,
1, 1,
200, 200,
"%", "%",
IntSetting.RENDERER_SPEED_LIMIT.key, ShortSetting.RENDERER_SPEED_LIMIT.key,
IntSetting.RENDERER_SPEED_LIMIT.defaultValue ShortSetting.RENDERER_SPEED_LIMIT.defaultValue
) )
) )
add( add(
@ -182,11 +177,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
sl.apply { sl.apply {
add( add(
SwitchSetting( SwitchSetting(
IntSetting.USE_DOCKED_MODE, BooleanSetting.USE_DOCKED_MODE,
R.string.use_docked_mode, R.string.use_docked_mode,
R.string.use_docked_mode_description, R.string.use_docked_mode_description,
IntSetting.USE_DOCKED_MODE.key, BooleanSetting.USE_DOCKED_MODE.key,
IntSetting.USE_DOCKED_MODE.defaultValue BooleanSetting.USE_DOCKED_MODE.defaultValue
) )
) )
add( add(
@ -222,11 +217,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
) )
add( add(
DateTimeSetting( DateTimeSetting(
StringSetting.CUSTOM_RTC, LongSetting.CUSTOM_RTC,
R.string.set_custom_rtc, R.string.set_custom_rtc,
0, 0,
StringSetting.CUSTOM_RTC.key, LongSetting.CUSTOM_RTC.key,
StringSetting.CUSTOM_RTC.defaultValue LongSetting.CUSTOM_RTC.defaultValue
) )
) )
} }
@ -314,38 +309,38 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
) )
add( add(
SwitchSetting( 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,
R.string.use_disk_shader_cache_description, R.string.use_disk_shader_cache_description,
IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key, BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
) )
) )
add( add(
SwitchSetting( SwitchSetting(
IntSetting.RENDERER_FORCE_MAX_CLOCK, BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
R.string.renderer_force_max_clock, R.string.renderer_force_max_clock,
R.string.renderer_force_max_clock_description, R.string.renderer_force_max_clock_description,
IntSetting.RENDERER_FORCE_MAX_CLOCK.key, BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key,
IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue BooleanSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
) )
) )
add( add(
SwitchSetting( SwitchSetting(
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS, BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
R.string.renderer_asynchronous_shaders, R.string.renderer_asynchronous_shaders,
R.string.renderer_asynchronous_shaders_description, R.string.renderer_asynchronous_shaders_description,
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key, BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
) )
) )
add( add(
SwitchSetting( SwitchSetting(
IntSetting.RENDERER_REACTIVE_FLUSHING, BooleanSetting.RENDERER_REACTIVE_FLUSHING,
R.string.renderer_reactive_flushing, R.string.renderer_reactive_flushing,
R.string.renderer_reactive_flushing_description, R.string.renderer_reactive_flushing_description,
IntSetting.RENDERER_REACTIVE_FLUSHING.key, BooleanSetting.RENDERER_REACTIVE_FLUSHING.key,
IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue BooleanSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
) )
) )
} }
@ -355,26 +350,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
sl.apply { sl.apply {
add( add(
StringSingleChoiceSetting( SingleChoiceSetting(
StringSetting.AUDIO_OUTPUT_ENGINE, IntSetting.AUDIO_OUTPUT_ENGINE,
R.string.audio_output_engine, R.string.audio_output_engine,
0, 0,
settingsActivity.resources.getStringArray(R.array.outputEngineEntries), R.array.outputEngineEntries,
settingsActivity.resources.getStringArray(R.array.outputEngineValues), R.array.outputEngineValues,
StringSetting.AUDIO_OUTPUT_ENGINE.key, IntSetting.AUDIO_OUTPUT_ENGINE.key,
StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue IntSetting.AUDIO_OUTPUT_ENGINE.defaultValue
) )
) )
add( add(
SliderSetting( SliderSetting(
IntSetting.AUDIO_VOLUME, ByteSetting.AUDIO_VOLUME,
R.string.audio_volume, R.string.audio_volume,
R.string.audio_volume_description, R.string.audio_volume_description,
0, 0,
100, 100,
"%", "%",
IntSetting.AUDIO_VOLUME.key, ByteSetting.AUDIO_VOLUME.key,
IntSetting.AUDIO_VOLUME.defaultValue ByteSetting.AUDIO_VOLUME.defaultValue
) )
) )
} }
@ -384,19 +379,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme)) settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
sl.apply { sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting { val theme: AbstractIntSetting = object : AbstractIntSetting {
override var int: Int override val int: Int
get() = preferences.getInt(Settings.PREF_THEME, 0) get() = preferences.getInt(Settings.PREF_THEME, 0)
set(value) {
preferences.edit() override fun setInt(value: Int) {
.putInt(Settings.PREF_THEME, value) preferences.edit()
.apply() .putInt(Settings.PREF_THEME, value)
settingsActivity.recreate() .apply()
} settingsActivity.recreate()
}
override val key: String? = null override val key: String? = null
override val section: String? = null override val category = Settings.Category.UiGeneral
override val isRuntimeEditable: Boolean = false override val isRuntimeModifiable: Boolean = false
override val valueAsString: String
get() = preferences.getInt(Settings.PREF_THEME, 0).toString()
override val defaultValue: Any = 0 override val defaultValue: Any = 0
} }
@ -423,19 +418,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
} }
val themeMode: AbstractIntSetting = object : AbstractIntSetting { val themeMode: AbstractIntSetting = object : AbstractIntSetting {
override var int: Int override val int: Int
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
set(value) {
preferences.edit() override fun setInt(value: Int) {
.putInt(Settings.PREF_THEME_MODE, value) preferences.edit()
.apply() .putInt(Settings.PREF_THEME_MODE, value)
ThemeHelper.setThemeMode(settingsActivity) .apply()
} ThemeHelper.setThemeMode(settingsActivity)
}
override val key: String? = null override val key: String? = null
override val section: String? = null override val category = Settings.Category.UiGeneral
override val isRuntimeEditable: Boolean = false override val isRuntimeModifiable: Boolean = false
override val valueAsString: String
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString()
override val defaultValue: Any = -1 override val defaultValue: Any = -1
} }
@ -450,20 +445,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
) )
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
override var boolean: Boolean override val boolean: Boolean
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
set(value) {
preferences.edit() override fun setBoolean(value: Boolean) {
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value) preferences.edit()
.apply() .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
settingsActivity.recreate() .apply()
} settingsActivity.recreate()
}
override val key: String? = null override val key: String? = null
override val section: String? = null override val category = Settings.Category.UiGeneral
override val isRuntimeEditable: Boolean = false override val isRuntimeModifiable: Boolean = false
override val valueAsString: String
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
.toString()
override val defaultValue: Any = false override val defaultValue: Any = false
} }
@ -494,11 +488,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
) )
add( add(
SwitchSetting( SwitchSetting(
IntSetting.RENDERER_DEBUG, BooleanSetting.RENDERER_DEBUG,
R.string.renderer_debug, R.string.renderer_debug,
R.string.renderer_debug_description, R.string.renderer_debug_description,
IntSetting.RENDERER_DEBUG.key, BooleanSetting.RENDERER_DEBUG.key,
IntSetting.RENDERER_DEBUG.defaultValue BooleanSetting.RENDERER_DEBUG.defaultValue
) )
) )
@ -514,17 +508,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
) )
val fastmem = object : AbstractBooleanSetting { val fastmem = object : AbstractBooleanSetting {
override var boolean: Boolean override val boolean: Boolean
get() = get() =
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
set(value) {
BooleanSetting.FASTMEM.boolean = value override fun setBoolean(value: Boolean) {
BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value BooleanSetting.FASTMEM.setBoolean(value)
} BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
}
override val key: String? = null override val key: String? = null
override val section: String = Settings.SECTION_CPU override val category = Settings.Category.Cpu
override val isRuntimeEditable: Boolean = false override val isRuntimeModifiable: Boolean = false
override val valueAsString: String = ""
override val defaultValue: Any = true override val defaultValue: Any = true
} }
add( add(

View file

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.features.settings.ui 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 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
/** /**
@ -36,21 +35,6 @@ interface SettingsFragmentView {
*/ */
fun loadSubMenu(menuKey: String) 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. * Have the fragment tell the containing Activity that a setting was modified.
*/ */

View file

@ -29,7 +29,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
} }
binding.textSettingValue.visibility = View.VISIBLE binding.textSettingValue.visibility = View.VISIBLE
val epochTime = setting.value.toLong() val epochTime = setting.value
val instant = Instant.ofEpochMilli(epochTime * 1000) val instant = Instant.ofEpochMilli(epochTime * 1000)
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)

View file

@ -3,18 +3,15 @@
package org.yuzu.yuzu_emu.features.settings.utils package org.yuzu.yuzu_emu.features.settings.utils
import android.widget.Toast
import java.io.* import java.io.*
import java.util.*
import org.ini4j.Wini import org.ini4j.Wini
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.* 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.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log 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. * 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 { object SettingsFile {
const val FILE_NAME_CONFIG = "config" 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 * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
* telling why it failed. * telling why it failed.
* *
* @param fileName The target filename without a path or extension. * @param 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( fun saveFile(fileName: String) {
fileName: String,
sections: TreeMap<String, SettingSection>,
view: SettingsActivityView
) {
val ini = getSettingsFile(fileName) val ini = getSettingsFile(fileName)
try { try {
val writer = Wini(ini) val wini = Wini(ini)
val keySet: Set<String> = sections.keys for (specificCategory in Settings.Category.values()) {
for (key in keySet) { val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
val section = sections[key] for (setting in Settings.settingsList) {
writeSection(writer, section!!) 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) { } catch (e: IOException) {
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message) Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
view.showToastMessage( val context = YuzuApplication.appContext
YuzuApplication.appContext Toast.makeText(
.getString(R.string.error_saving, fileName, e.message), context,
false context.getString(R.string.error_saving, fileName, e.message),
) Toast.LENGTH_SHORT
).show()
} }
} }
fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) { fun getSettingsFile(fileName: String): File =
val sortedSections: Set<String> = TreeSet(sections.keys) File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
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)
}
}
}
} }

View file

@ -39,7 +39,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings 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.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
@ -54,7 +53,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private val homeViewModel: HomeViewModel by viewModels() private val homeViewModel: HomeViewModel by viewModels()
private val gamesViewModel: GamesViewModel by viewModels() private val gamesViewModel: GamesViewModel by viewModels()
private val settingsViewModel: SettingsViewModel by viewModels()
override var themeId: Int = 0 override var themeId: Int = 0
@ -62,8 +60,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val splashScreen = installSplashScreen() val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
settingsViewModel.settings.loadSettings()
ThemeHelper.setTheme(this) ThemeHelper.setTheme(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -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]
}
}

View file

@ -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
}

View file

@ -14,6 +14,8 @@ add_library(yuzu-android SHARED
id_cache.cpp id_cache.cpp
id_cache.h id_cache.h
native.cpp native.cpp
native_config.cpp
uisettings.cpp
) )
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})

View file

@ -16,18 +16,20 @@
#include "input_common/main.h" #include "input_common/main.h"
#include "jni/config.h" #include "jni/config.h"
#include "jni/default_ini.h" #include "jni/default_ini.h"
#include "uisettings.h"
namespace FS = Common::FS; namespace FS = Common::FS;
Config::Config(std::optional<std::filesystem::path> config_path) Config::Config(const std::string& config_name, ConfigType config_type)
: config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")}, : type(config_type), global{config_type == ConfigType::GlobalConfig} {
config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} { Initialize(config_name);
Reload();
} }
Config::~Config() = default; Config::~Config() = default;
bool Config::LoadINI(const std::string& default_contents, bool retry) { 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); const auto config_loc_str = FS::PathToUTF8String(config_loc);
if (config->ParseError() < 0) { if (config->ParseError() < 0) {
if (retry) { if (retry) {
@ -301,9 +303,28 @@ void Config::ReadValues() {
// Network // Network
ReadSetting("Network", Settings::values.network_interface); 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); LoadINI(DefaultINI::android_config_file);
ReadValues(); ReadValues();
} }

View file

@ -13,25 +13,35 @@
class INIReader; class INIReader;
class Config { class Config {
std::filesystem::path config_loc;
std::unique_ptr<INIReader> config;
bool LoadINI(const std::string& default_contents = "", bool retry = true); bool LoadINI(const std::string& default_contents = "", bool retry = true);
void ReadValues();
public: 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(); ~Config();
void Reload(); void Initialize(const std::string& config_name);
private: 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 group The name of the INI group
* @param setting The yuzu setting to modify * @param setting The yuzu setting to modify
*/ */
template <typename Type, bool ranged> template <typename Type, bool ranged>
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting); void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
void ReadValues();
const ConfigType type;
std::unique_ptr<INIReader> config;
std::string config_loc;
const bool global;
}; };

View file

@ -824,34 +824,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl
Config{}; 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, void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
jstring j_game_id) { jstring j_game_id) {
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);

View 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"

View 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

View 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

View file

@ -243,10 +243,10 @@
<item>@string/cubeb</item> <item>@string/cubeb</item>
<item>@string/string_null</item> <item>@string/string_null</item>
</string-array> </string-array>
<string-array name="outputEngineValues"> <integer-array name="outputEngineValues">
<item>auto</item> <item>0</item>
<item>cubeb</item> <item>1</item>
<item>null</item> <item>3</item>
</string-array> </integer-array>
</resources> </resources>

View file

@ -200,6 +200,7 @@
<string name="ini_saved">Saved settings</string> <string name="ini_saved">Saved settings</string>
<string name="gameid_saved">Saved settings for %1$s</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="error_saving">Error saving %1$s.ini: %2$s</string>
<string name="unimplemented_menu">Unimplemented Menu</string>
<string name="loading">Loading…</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_setting_confirmation">Do you want to reset this setting back to its default value?</string>
<string name="reset_to_default">Reset to default</string> <string name="reset_to_default">Reset to default</string>

View file

@ -159,6 +159,8 @@ float Volume() {
const char* TranslateCategory(Category category) { const char* TranslateCategory(Category category) {
switch (category) { switch (category) {
case Category::Android:
return "Android";
case Category::Audio: case Category::Audio:
return "Audio"; return "Audio";
case Category::Core: case Category::Core:

View file

@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ
: label{name}, category{category_}, id{linkage.count}, save{save_}, : label{name}, category{category_}, id{linkage.count}, save{save_},
runtime_modifiable{runtime_modifiable_}, specialization{specialization_}, runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
other_setting{other_setting_} { other_setting{other_setting_} {
linkage.by_key.insert({name, this});
linkage.by_category[category].push_back(this); linkage.by_category[category].push_back(this);
linkage.count++; linkage.count++;
} }

View file

@ -12,6 +12,7 @@
namespace Settings { namespace Settings {
enum class Category : u32 { enum class Category : u32 {
Android,
Audio, Audio,
Core, Core,
Cpu, Cpu,
@ -68,6 +69,7 @@ public:
explicit Linkage(u32 initial_count = 0); explicit Linkage(u32 initial_count = 0);
~Linkage(); ~Linkage();
std::map<Category, std::vector<BasicSetting*>> by_category{}; std::map<Category, std::vector<BasicSetting*>> by_category{};
std::map<std::string, Settings::BasicSetting*> by_key{};
std::vector<std::function<void()>> restore_functions{}; std::vector<std::function<void()>> restore_functions{};
u32 count; u32 count;
}; };