mirror of
https://github.com/starr-dusT/yuzu-mainline
synced 2024-03-05 21:12:25 -08:00
Merge pull request #11380 from t895/settings-integration
android: Settings rework
This commit is contained in:
commit
44bce11853
@ -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
|
||||||
|
@ -46,7 +46,7 @@ class YuzuApplication : Application() {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
application = this
|
application = this
|
||||||
documentsTree = DocumentsTree()
|
documentsTree = DocumentsTree()
|
||||||
DirectoryInitialization.start(applicationContext)
|
DirectoryInitialization.start()
|
||||||
GpuDriverHelper.initializeDriverParameters(applicationContext)
|
GpuDriverHelper.initializeDriverParameters(applicationContext)
|
||||||
NativeLibrary.logDeviceInfo()
|
NativeLibrary.logDeviceInfo()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
@ -91,9 +85,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
|
|
||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||||
val navController = navHostFragment.navController
|
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
|
||||||
navController
|
|
||||||
.setGraph(R.navigation.emulation_navigation, intent.extras)
|
|
||||||
|
|
||||||
isActivityRecreated = savedInstanceState != null
|
isActivityRecreated = savedInstanceState != null
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
|
||||||
}
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,22 @@
|
|||||||
|
|
||||||
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 androidDefault: Any?
|
||||||
|
get() = null
|
||||||
|
val valueAsString: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
val isRuntimeModifiable: Boolean
|
||||||
|
get() = NativeConfig.getIsRuntimeModifiable(key)
|
||||||
|
|
||||||
|
val pairedSettingKey: String
|
||||||
|
get() = NativeConfig.getPairedSettingKey(key)
|
||||||
|
|
||||||
|
fun reset()
|
||||||
}
|
}
|
||||||
|
@ -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
|
package org.yuzu.yuzu_emu.features.settings.model
|
||||||
|
|
||||||
interface AbstractStringSetting : AbstractSetting {
|
interface AbstractStringSetting : AbstractSetting {
|
||||||
var string: String
|
val string: String
|
||||||
|
|
||||||
|
fun setString(value: String)
|
||||||
}
|
}
|
||||||
|
@ -3,41 +3,37 @@
|
|||||||
|
|
||||||
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
|
override val androidDefault: Boolean? = null
|
||||||
) : 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, false),
|
||||||
|
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, false),
|
||||||
|
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 {
|
||||||
|
androidDefault ?: 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 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
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 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,139 +3,37 @@
|
|||||||
|
|
||||||
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
|
override val androidDefault: Int? = null
|
||||||
) : 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, 0),
|
||||||
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 {
|
||||||
|
androidDefault ?: 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 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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,104 +4,74 @@
|
|||||||
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
)
|
)
|
||||||
|
|
||||||
SettingsFile.saveCustomGameSettings(gameId, sections)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SECTION_GENERAL = "General"
|
const val SECTION_GENERAL = "General"
|
||||||
const val SECTION_SYSTEM = "System"
|
const val SECTION_SYSTEM = "System"
|
||||||
const val SECTION_RENDERER = "Renderer"
|
const val SECTION_RENDERER = "Renderer"
|
||||||
@ -154,8 +124,6 @@ class Settings {
|
|||||||
const val PREF_THEME_MODE = "ThemeMode"
|
const val PREF_THEME_MODE = "ThemeMode"
|
||||||
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
|
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
|
||||||
|
|
||||||
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
|
||||||
|
|
||||||
val overlayPreferences = listOf(
|
val overlayPreferences = listOf(
|
||||||
PREF_OVERLAY_VERSION,
|
PREF_OVERLAY_VERSION,
|
||||||
PREF_CONTROL_SCALE,
|
PREF_CONTROL_SCALE,
|
||||||
@ -183,16 +151,4 @@ class Settings {
|
|||||||
const val LayoutOption_Unspecified = 0
|
const val LayoutOption_Unspecified = 0
|
||||||
const val LayoutOption_MobilePortrait = 4
|
const val LayoutOption_MobilePortrait = 4
|
||||||
const val LayoutOption_MobileLandscape = 5
|
const val LayoutOption_MobileLandscape = 5
|
||||||
|
|
||||||
init {
|
|
||||||
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
|
|
||||||
listOf(
|
|
||||||
SECTION_GENERAL,
|
|
||||||
SECTION_SYSTEM,
|
|
||||||
SECTION_RENDERER,
|
|
||||||
SECTION_AUDIO,
|
|
||||||
SECTION_CPU
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
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 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,29 +3,16 @@
|
|||||||
|
|
||||||
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.AbstractSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
|
||||||
|
|
||||||
class DateTimeSetting(
|
class DateTimeSetting(
|
||||||
setting: AbstractSetting?,
|
private val longSetting: AbstractLongSetting,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int
|
||||||
val key: String? = null,
|
) : SettingsItem(longSetting, titleId, descriptionId) {
|
||||||
private val defaultValue: String? = null
|
|
||||||
) : SettingsItem(setting, titleId, descriptionId) {
|
|
||||||
override val type = TYPE_DATETIME_SETTING
|
override val type = TYPE_DATETIME_SETTING
|
||||||
|
|
||||||
val value: String
|
var value: Long
|
||||||
get() = if (setting != null) {
|
get() = longSetting.long
|
||||||
val setting = setting as AbstractStringSetting
|
set(value) = (setting as AbstractLongSetting).setLong(value)
|
||||||
setting.string
|
|
||||||
} else {
|
|
||||||
defaultValue!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSelectedValue(datetime: String): AbstractStringSetting {
|
|
||||||
val stringSetting = setting as AbstractStringSetting
|
|
||||||
stringSetting.string = datetime
|
|
||||||
return stringSetting
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view
|
|||||||
|
|
||||||
class HeaderSetting(
|
class HeaderSetting(
|
||||||
titleId: Int
|
titleId: Int
|
||||||
) : SettingsItem(null, titleId, 0) {
|
) : SettingsItem(emptySetting, titleId, 0) {
|
||||||
override val type = TYPE_HEADER
|
override val type = TYPE_HEADER
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,6 @@ class RunnableSetting(
|
|||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val isRuntimeRunnable: Boolean,
|
val isRuntimeRunnable: Boolean,
|
||||||
val runnable: () -> Unit
|
val runnable: () -> Unit
|
||||||
) : SettingsItem(null, titleId, descriptionId) {
|
) : SettingsItem(emptySetting, titleId, descriptionId) {
|
||||||
override val type = TYPE_RUNNABLE
|
override val type = TYPE_RUNNABLE
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,15 @@
|
|||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||||
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.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.ShortSetting
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
|
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
|
||||||
@ -14,7 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
|||||||
* file.)
|
* file.)
|
||||||
*/
|
*/
|
||||||
abstract class SettingsItem(
|
abstract class SettingsItem(
|
||||||
var setting: AbstractSetting?,
|
val setting: AbstractSetting,
|
||||||
val nameId: Int,
|
val nameId: Int,
|
||||||
val descriptionId: Int
|
val descriptionId: Int
|
||||||
) {
|
) {
|
||||||
@ -23,7 +31,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
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -35,5 +43,240 @@ abstract class SettingsItem(
|
|||||||
const val TYPE_STRING_SINGLE_CHOICE = 5
|
const val TYPE_STRING_SINGLE_CHOICE = 5
|
||||||
const val TYPE_DATETIME_SETTING = 6
|
const val TYPE_DATETIME_SETTING = 6
|
||||||
const val TYPE_RUNNABLE = 7
|
const val TYPE_RUNNABLE = 7
|
||||||
|
|
||||||
|
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||||
|
|
||||||
|
val emptySetting = object : AbstractSetting {
|
||||||
|
override val key: String = ""
|
||||||
|
override val category: Settings.Category = Settings.Category.Ui
|
||||||
|
override val defaultValue: Any = false
|
||||||
|
override fun reset() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension for putting SettingsItems into a hashmap without repeating yourself
|
||||||
|
fun HashMap<String, SettingsItem>.put(item: SettingsItem) {
|
||||||
|
put(item.setting.key, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of all general
|
||||||
|
val settingsItems = HashMap<String, SettingsItem>().apply {
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||||
|
R.string.frame_limit_enable,
|
||||||
|
R.string.frame_limit_enable_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SliderSetting(
|
||||||
|
ShortSetting.RENDERER_SPEED_LIMIT,
|
||||||
|
R.string.frame_limit_slider,
|
||||||
|
R.string.frame_limit_slider_description,
|
||||||
|
1,
|
||||||
|
200,
|
||||||
|
"%"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.CPU_ACCURACY,
|
||||||
|
R.string.cpu_accuracy,
|
||||||
|
0,
|
||||||
|
R.array.cpuAccuracyNames,
|
||||||
|
R.array.cpuAccuracyValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.PICTURE_IN_PICTURE,
|
||||||
|
R.string.picture_in_picture,
|
||||||
|
R.string.picture_in_picture_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.USE_DOCKED_MODE,
|
||||||
|
R.string.use_docked_mode,
|
||||||
|
R.string.use_docked_mode_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.REGION_INDEX,
|
||||||
|
R.string.emulated_region,
|
||||||
|
0,
|
||||||
|
R.array.regionNames,
|
||||||
|
R.array.regionValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.LANGUAGE_INDEX,
|
||||||
|
R.string.emulated_language,
|
||||||
|
0,
|
||||||
|
R.array.languageNames,
|
||||||
|
R.array.languageValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.USE_CUSTOM_RTC,
|
||||||
|
R.string.use_custom_rtc,
|
||||||
|
R.string.use_custom_rtc_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.RENDERER_ACCURACY,
|
||||||
|
R.string.renderer_accuracy,
|
||||||
|
0,
|
||||||
|
R.array.rendererAccuracyNames,
|
||||||
|
R.array.rendererAccuracyValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.RENDERER_RESOLUTION,
|
||||||
|
R.string.renderer_resolution,
|
||||||
|
0,
|
||||||
|
R.array.rendererResolutionNames,
|
||||||
|
R.array.rendererResolutionValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.RENDERER_VSYNC,
|
||||||
|
R.string.renderer_vsync,
|
||||||
|
0,
|
||||||
|
R.array.rendererVSyncNames,
|
||||||
|
R.array.rendererVSyncValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.RENDERER_SCALING_FILTER,
|
||||||
|
R.string.renderer_scaling_filter,
|
||||||
|
0,
|
||||||
|
R.array.rendererScalingFilterNames,
|
||||||
|
R.array.rendererScalingFilterValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.RENDERER_ANTI_ALIASING,
|
||||||
|
R.string.renderer_anti_aliasing,
|
||||||
|
0,
|
||||||
|
R.array.rendererAntiAliasingNames,
|
||||||
|
R.array.rendererAntiAliasingValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||||
|
R.string.renderer_screen_layout,
|
||||||
|
0,
|
||||||
|
R.array.rendererScreenLayoutNames,
|
||||||
|
R.array.rendererScreenLayoutValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.RENDERER_ASPECT_RATIO,
|
||||||
|
R.string.renderer_aspect_ratio,
|
||||||
|
0,
|
||||||
|
R.array.rendererAspectRatioNames,
|
||||||
|
R.array.rendererAspectRatioValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
|
||||||
|
R.string.use_disk_shader_cache,
|
||||||
|
R.string.use_disk_shader_cache_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
|
||||||
|
R.string.renderer_force_max_clock,
|
||||||
|
R.string.renderer_force_max_clock_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
||||||
|
R.string.renderer_asynchronous_shaders,
|
||||||
|
R.string.renderer_asynchronous_shaders_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_REACTIVE_FLUSHING,
|
||||||
|
R.string.renderer_reactive_flushing,
|
||||||
|
R.string.renderer_reactive_flushing_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.AUDIO_OUTPUT_ENGINE,
|
||||||
|
R.string.audio_output_engine,
|
||||||
|
0,
|
||||||
|
R.array.outputEngineEntries,
|
||||||
|
R.array.outputEngineValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SliderSetting(
|
||||||
|
ByteSetting.AUDIO_VOLUME,
|
||||||
|
R.string.audio_volume,
|
||||||
|
R.string.audio_volume_description,
|
||||||
|
0,
|
||||||
|
100,
|
||||||
|
"%"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.RENDERER_BACKEND,
|
||||||
|
R.string.renderer_api,
|
||||||
|
0,
|
||||||
|
R.array.rendererApiNames,
|
||||||
|
R.array.rendererApiValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_DEBUG,
|
||||||
|
R.string.renderer_debug,
|
||||||
|
R.string.renderer_debug_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.CPU_DEBUG_MODE,
|
||||||
|
R.string.cpu_debug_mode,
|
||||||
|
R.string.cpu_debug_mode_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val fastmem = object : AbstractBooleanSetting {
|
||||||
|
override val boolean: Boolean
|
||||||
|
get() =
|
||||||
|
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
|
||||||
|
|
||||||
|
override fun setBoolean(value: Boolean) {
|
||||||
|
BooleanSetting.FASTMEM.setBoolean(value)
|
||||||
|
BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val key: String = FASTMEM_COMBINED
|
||||||
|
override val category = Settings.Category.Cpu
|
||||||
|
override val isRuntimeModifiable: Boolean = false
|
||||||
|
override val defaultValue: Boolean = true
|
||||||
|
override fun reset() = setBoolean(defaultValue)
|
||||||
|
}
|
||||||
|
put(SwitchSetting(fastmem, R.string.fastmem, 0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,36 +4,27 @@
|
|||||||
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.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||||
|
|
||||||
class SingleChoiceSetting(
|
class SingleChoiceSetting(
|
||||||
setting: AbstractIntSetting?,
|
setting: AbstractSetting,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val choicesId: Int,
|
val choicesId: Int,
|
||||||
val valuesId: Int,
|
val valuesId: Int
|
||||||
val key: String? = null,
|
|
||||||
val defaultValue: Int? = null
|
|
||||||
) : SettingsItem(setting, titleId, descriptionId) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_SINGLE_CHOICE
|
override val type = TYPE_SINGLE_CHOICE
|
||||||
|
|
||||||
val selectedValue: Int
|
var selectedValue: Int
|
||||||
get() = if (setting != null) {
|
get() {
|
||||||
val setting = setting as AbstractIntSetting
|
return when (setting) {
|
||||||
setting.int
|
is AbstractIntSetting -> setting.int
|
||||||
} else {
|
else -> -1
|
||||||
defaultValue!!
|
}
|
||||||
}
|
}
|
||||||
|
set(value) {
|
||||||
/**
|
when (setting) {
|
||||||
* Write a value to the backing int. If that int was previously null,
|
is AbstractIntSetting -> setting.setInt(value)
|
||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
}
|
||||||
*
|
|
||||||
* @param selection New value of the int.
|
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
|
||||||
fun setSelectedValue(selection: Int): AbstractIntSetting {
|
|
||||||
val intSetting = setting as AbstractIntSetting
|
|
||||||
intSetting.int = selection
|
|
||||||
return intSetting
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,60 +3,39 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import kotlin.math.roundToInt
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
|
||||||
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.utils.Log
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class SliderSetting(
|
class SliderSetting(
|
||||||
setting: AbstractSetting?,
|
setting: AbstractSetting,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val min: Int,
|
val min: Int,
|
||||||
val max: Int,
|
val max: Int,
|
||||||
val units: String,
|
val units: String
|
||||||
val key: String? = null,
|
|
||||||
val defaultValue: Int? = null
|
|
||||||
) : SettingsItem(setting, titleId, descriptionId) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_SLIDER
|
override val type = TYPE_SLIDER
|
||||||
|
|
||||||
val selectedValue: Int
|
var selectedValue: Int
|
||||||
get() {
|
get() {
|
||||||
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 -> -1
|
||||||
Log.error("[SliderSetting] Error casting setting type.")
|
}
|
||||||
-1
|
}
|
||||||
|
set(value) {
|
||||||
|
when (setting) {
|
||||||
|
is AbstractByteSetting -> setting.setByte(value.toByte())
|
||||||
|
is AbstractShortSetting -> setting.setShort(value.toShort())
|
||||||
|
is AbstractIntSetting -> setting.setInt(value)
|
||||||
|
is AbstractFloatSetting -> setting.setFloat(value.toFloat())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a value to the backing int. If that int was previously null,
|
|
||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
|
||||||
*
|
|
||||||
* @param selection New value of the int.
|
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
|
||||||
fun setSelectedValue(selection: Int): AbstractIntSetting {
|
|
||||||
val intSetting = setting as AbstractIntSetting
|
|
||||||
intSetting.int = selection
|
|
||||||
return intSetting
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a value to the backing float. If that float was previously null,
|
|
||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
|
||||||
*
|
|
||||||
* @param selection New value of the float.
|
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
|
||||||
fun setSelectedValue(selection: Float): AbstractFloatSetting {
|
|
||||||
val floatSetting = setting as AbstractFloatSetting
|
|
||||||
floatSetting.float = selection
|
|
||||||
return floatSetting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -3,57 +3,31 @@
|
|||||||
|
|
||||||
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.AbstractSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
||||||
|
|
||||||
class StringSingleChoiceSetting(
|
class StringSingleChoiceSetting(
|
||||||
setting: AbstractSetting?,
|
private val stringSetting: AbstractStringSetting,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val choices: Array<String>,
|
val choices: Array<String>,
|
||||||
val values: Array<String>?,
|
val values: Array<String>
|
||||||
val key: String? = null,
|
) : SettingsItem(stringSetting, titleId, descriptionId) {
|
||||||
private val defaultValue: String? = null
|
|
||||||
) : SettingsItem(setting, titleId, descriptionId) {
|
|
||||||
override val type = TYPE_STRING_SINGLE_CHOICE
|
override val type = TYPE_STRING_SINGLE_CHOICE
|
||||||
|
|
||||||
fun getValueAt(index: Int): String? {
|
fun getValueAt(index: Int): String =
|
||||||
if (values == null) return null
|
if (index >= 0 && index < values.size) values[index] else ""
|
||||||
return if (index >= 0 && index < values.size) {
|
|
||||||
values[index]
|
var selectedValue: String
|
||||||
} else {
|
get() = stringSetting.string
|
||||||
""
|
set(value) = stringSetting.setString(value)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val selectedValue: String
|
|
||||||
get() = if (setting != null) {
|
|
||||||
val setting = setting as AbstractStringSetting
|
|
||||||
setting.string
|
|
||||||
} else {
|
|
||||||
defaultValue!!
|
|
||||||
}
|
|
||||||
val selectValueIndex: Int
|
val selectValueIndex: Int
|
||||||
get() {
|
get() {
|
||||||
val selectedValue = selectedValue
|
for (i in values.indices) {
|
||||||
for (i in values!!.indices) {
|
|
||||||
if (values[i] == selectedValue) {
|
if (values[i] == selectedValue) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a value to the backing int. If that int was previously null,
|
|
||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
|
||||||
*
|
|
||||||
* @param selection New value of the int.
|
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
|
||||||
fun setSelectedValue(selection: String): AbstractStringSetting {
|
|
||||||
val stringSetting = setting as AbstractStringSetting
|
|
||||||
stringSetting.string = selection
|
|
||||||
return stringSetting
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@ class SubmenuSetting(
|
|||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
val menuKey: String
|
val menuKey: String
|
||||||
) : SettingsItem(null, titleId, descriptionId) {
|
) : SettingsItem(emptySetting, titleId, descriptionId) {
|
||||||
override val type = TYPE_SUBMENU
|
override val type = TYPE_SUBMENU
|
||||||
}
|
}
|
||||||
|
@ -10,53 +10,22 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
|||||||
class SwitchSetting(
|
class SwitchSetting(
|
||||||
setting: AbstractSetting,
|
setting: AbstractSetting,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int
|
||||||
val key: String? = null,
|
|
||||||
val defaultValue: Any? = null
|
|
||||||
) : SettingsItem(setting, titleId, descriptionId) {
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
override val type = TYPE_SWITCH
|
override val type = TYPE_SWITCH
|
||||||
|
|
||||||
val isChecked: Boolean
|
var checked: Boolean
|
||||||
get() {
|
get() {
|
||||||
if (setting == null) {
|
return when (setting) {
|
||||||
return defaultValue as Boolean
|
is AbstractIntSetting -> setting.int == 1
|
||||||
}
|
is AbstractBooleanSetting -> setting.boolean
|
||||||
|
else -> false
|
||||||
// Try integer setting
|
}
|
||||||
try {
|
}
|
||||||
val setting = setting as AbstractIntSetting
|
set(value) {
|
||||||
return setting.int == 1
|
when (setting) {
|
||||||
} catch (_: ClassCastException) {
|
is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
|
||||||
}
|
is AbstractBooleanSetting -> setting.setBoolean(value)
|
||||||
|
}
|
||||||
// Try boolean setting
|
|
||||||
try {
|
|
||||||
val setting = setting as AbstractBooleanSetting
|
|
||||||
return setting.boolean
|
|
||||||
} catch (_: ClassCastException) {
|
|
||||||
}
|
|
||||||
return defaultValue as Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a value to the backing boolean. If that boolean was previously null,
|
|
||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
|
||||||
*
|
|
||||||
* @param checked Pretty self explanatory.
|
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
|
||||||
fun setChecked(checked: Boolean): AbstractSetting {
|
|
||||||
// Try integer setting
|
|
||||||
try {
|
|
||||||
val setting = setting as AbstractIntSetting
|
|
||||||
setting.int = if (checked) 1 else 0
|
|
||||||
return setting
|
|
||||||
} catch (_: ClassCastException) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try boolean setting
|
|
||||||
val setting = setting as AbstractBooleanSetting
|
|
||||||
setting.boolean = checked
|
|
||||||
return setting
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,7 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.features.settings.ui
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@ -16,28 +13,24 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
import androidx.navigation.navArgs
|
||||||
import com.google.android.material.color.MaterialColors
|
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.fragments.ResetSettingsDialogFragment
|
||||||
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.*
|
import org.yuzu.yuzu_emu.utils.*
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
private val presenter = SettingsActivityPresenter(this)
|
|
||||||
|
|
||||||
private lateinit var binding: ActivitySettingsBinding
|
private lateinit var binding: ActivitySettingsBinding
|
||||||
|
|
||||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
private val args by navArgs<SettingsActivityArgs>()
|
||||||
|
|
||||||
override val settings: Settings get() = settingsViewModel.settings
|
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
ThemeHelper.setTheme(this)
|
ThemeHelper.setTheme(this)
|
||||||
@ -47,16 +40,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
settingsViewModel.game = args.game
|
||||||
|
|
||||||
|
val navHostFragment =
|
||||||
|
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||||
|
navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras)
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
val launcher = intent
|
if (savedInstanceState != null) {
|
||||||
val gameID = launcher.getStringExtra(ARG_GAME_ID)
|
settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
|
||||||
val menuTag = launcher.getStringExtra(ARG_MENU_TAG)
|
}
|
||||||
presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
|
|
||||||
|
|
||||||
// Show "Back" button in the action bar for navigation
|
|
||||||
setSupportActionBar(binding.toolbarSettings)
|
|
||||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
|
||||||
|
|
||||||
if (InsetsHelper.getSystemGestureType(applicationContext) !=
|
if (InsetsHelper.getSystemGestureType(applicationContext) !=
|
||||||
InsetsHelper.GESTURE_NAVIGATION
|
InsetsHelper.GESTURE_NAVIGATION
|
||||||
@ -72,6 +66,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settingsViewModel.shouldRecreate.observe(this) {
|
||||||
|
if (it) {
|
||||||
|
settingsViewModel.setShouldRecreate(false)
|
||||||
|
recreate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settingsViewModel.shouldNavigateBack.observe(this) {
|
||||||
|
if (it) {
|
||||||
|
settingsViewModel.setShouldNavigateBack(false)
|
||||||
|
navigateBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
|
||||||
|
if (it) {
|
||||||
|
settingsViewModel.setShouldShowResetSettingsDialog(false)
|
||||||
|
ResetSettingsDialogFragment().show(
|
||||||
|
supportFragmentManager,
|
||||||
|
ResetSettingsDialogFragment.TAG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(
|
onBackPressedDispatcher.addCallback(
|
||||||
this,
|
this,
|
||||||
object : OnBackPressedCallback(true) {
|
object : OnBackPressedCallback(true) {
|
||||||
@ -82,34 +98,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
fun navigateBack() {
|
||||||
navigateBack()
|
val navHostFragment =
|
||||||
return true
|
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||||
}
|
if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
|
||||||
|
navHostFragment.navController.popBackStack()
|
||||||
private fun navigateBack() {
|
|
||||||
if (supportFragmentManager.backStackEntryCount > 0) {
|
|
||||||
supportFragmentManager.popBackStack()
|
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
||||||
val inflater = menuInflater
|
|
||||||
inflater.inflate(R.menu.menu_settings, menu)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
// Critical: If super method is not called, rotations will be busted.
|
// Critical: If super method is not called, rotations will be busted.
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
presenter.saveState(outState)
|
outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
presenter.onStart()
|
// TODO: Load custom settings contextually
|
||||||
|
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||||
|
DirectoryInitialization.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,131 +129,51 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||||||
*/
|
*/
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
presenter.onStop(isFinishing)
|
if (isFinishing && settingsViewModel.shouldSave) {
|
||||||
|
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||||
|
Settings.saveSettings()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
|
override fun onDestroy() {
|
||||||
if (!addToStack && settingsFragment != null) {
|
settingsViewModel.clear()
|
||||||
return
|
super.onDestroy()
|
||||||
}
|
|
||||||
|
|
||||||
val transaction = supportFragmentManager.beginTransaction()
|
|
||||||
if (addToStack) {
|
|
||||||
if (areSystemAnimationsEnabled()) {
|
|
||||||
transaction.setCustomAnimations(
|
|
||||||
R.anim.anim_settings_fragment_in,
|
|
||||||
R.anim.anim_settings_fragment_out,
|
|
||||||
0,
|
|
||||||
R.anim.anim_pop_settings_fragment_out
|
|
||||||
)
|
|
||||||
}
|
|
||||||
transaction.addToBackStack(null)
|
|
||||||
}
|
|
||||||
transaction.replace(
|
|
||||||
R.id.frame_content,
|
|
||||||
SettingsFragment.newInstance(menuTag, gameId),
|
|
||||||
FRAGMENT_TAG
|
|
||||||
)
|
|
||||||
transaction.commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun areSystemAnimationsEnabled(): Boolean {
|
|
||||||
val duration = android.provider.Settings.Global.getFloat(
|
|
||||||
contentResolver,
|
|
||||||
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
|
|
||||||
1f
|
|
||||||
)
|
|
||||||
val transition = android.provider.Settings.Global.getFloat(
|
|
||||||
contentResolver,
|
|
||||||
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
|
|
||||||
1f
|
|
||||||
)
|
|
||||||
return duration != 0f && transition != 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSettingsFileLoaded() {
|
|
||||||
val fragment: SettingsFragmentView? = settingsFragment
|
|
||||||
fragment?.loadSettingsList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSettingsFileNotFound() {
|
|
||||||
val fragment: SettingsFragmentView? = settingsFragment
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSettingsReset() {
|
fun onSettingsReset() {
|
||||||
// Prevents saving to a non-existent settings file
|
// Prevents saving to a non-existent settings file
|
||||||
presenter.onSettingsReset()
|
settingsViewModel.shouldSave = false
|
||||||
|
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setToolbarTitle(title: String) {
|
|
||||||
binding.toolbarSettingsLayout.title = title
|
|
||||||
}
|
|
||||||
|
|
||||||
private val settingsFragment: SettingsFragment?
|
|
||||||
get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
|
|
||||||
|
|
||||||
private fun setInsets() {
|
private fun setInsets() {
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(
|
ViewCompat.setOnApplyWindowInsetsListener(
|
||||||
binding.frameContent
|
binding.navigationBarShade
|
||||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
|
||||||
view.updatePadding(
|
|
||||||
left = barInsets.left + cutoutInsets.left,
|
|
||||||
right = barInsets.right + cutoutInsets.right
|
|
||||||
)
|
|
||||||
|
|
||||||
val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
|
val mlpShade = view.layoutParams as MarginLayoutParams
|
||||||
mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
|
|
||||||
mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
|
|
||||||
binding.appbarSettings.layoutParams = mlpAppBar
|
|
||||||
|
|
||||||
val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
|
|
||||||
mlpShade.height = barInsets.bottom
|
mlpShade.height = barInsets.bottom
|
||||||
binding.navigationBarShade.layoutParams = mlpShade
|
view.layoutParams = mlpShade
|
||||||
|
|
||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ARG_MENU_TAG = "menu_tag"
|
private const val KEY_SHOULD_SAVE = "should_save"
|
||||||
private const val ARG_GAME_ID = "game_id"
|
|
||||||
private const val FRAGMENT_TAG = "settings"
|
|
||||||
|
|
||||||
fun launch(context: Context, menuTag: String?, gameId: String?) {
|
|
||||||
val settings = Intent(context, SettingsActivity::class.java)
|
|
||||||
settings.putExtra(ARG_MENU_TAG, menuTag)
|
|
||||||
settings.putExtra(ARG_GAME_ID, gameId)
|
|
||||||
context.startActivity(settings)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
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
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
|
||||||
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
|
|
||||||
|
|
||||||
fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
|
|
||||||
this.menuTag = menuTag
|
|
||||||
this.gameId = gameId
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onStart() {
|
|
||||||
prepareDirectoriesIfNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadSettingsUI() {
|
|
||||||
if (!settings.isLoaded) {
|
|
||||||
if (!TextUtils.isEmpty(gameId)) {
|
|
||||||
settings.loadSettings(gameId, activityView)
|
|
||||||
} else {
|
|
||||||
settings.loadSettings(activityView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
activityView.showSettingsFragment(menuTag, false, gameId)
|
|
||||||
activityView.onSettingsFileLoaded()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun prepareDirectoriesIfNeeded() {
|
|
||||||
val configFile =
|
|
||||||
File(
|
|
||||||
"${DirectoryInitialization.userDirectory}/config/" +
|
|
||||||
"${SettingsFile.FILE_NAME_CONFIG}.ini"
|
|
||||||
)
|
|
||||||
if (!configFile.exists()) {
|
|
||||||
Log.error(
|
|
||||||
"${DirectoryInitialization.userDirectory}/config/" +
|
|
||||||
"${SettingsFile.FILE_NAME_CONFIG}.ini"
|
|
||||||
)
|
|
||||||
Log.error("yuzu config file could not be found!")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
|
||||||
DirectoryInitialization.start(activityView as Context)
|
|
||||||
}
|
|
||||||
loadSettingsUI()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onStop(finishing: Boolean) {
|
|
||||||
if (finishing && shouldSave) {
|
|
||||||
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
|
||||||
settings.saveSettings(activityView)
|
|
||||||
}
|
|
||||||
NativeLibrary.reloadSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSettingChanged() {
|
|
||||||
shouldSave = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSettingsReset() {
|
|
||||||
shouldSave = false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveState(outState: Bundle) {
|
|
||||||
outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val KEY_SHOULD_SAVE = "should_save"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstraction for the Activity that manages SettingsFragments.
|
|
||||||
*/
|
|
||||||
interface SettingsActivityView {
|
|
||||||
/**
|
|
||||||
* Show a new SettingsFragment.
|
|
||||||
*
|
|
||||||
* @param menuTag Identifier for the settings group that should be displayed.
|
|
||||||
* @param addToStack Whether or not this fragment should replace a previous one.
|
|
||||||
*/
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
fun onSettingsFileLoaded()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a load operation fails.
|
|
||||||
*/
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
fun finish()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by a containing Fragment to tell the Activity that a setting was changed;
|
|
||||||
* unless this has been called, the Activity will not save to disk.
|
|
||||||
*/
|
|
||||||
fun onSettingChanged()
|
|
||||||
}
|
|
@ -4,51 +4,54 @@
|
|||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.features.settings.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.icu.util.Calendar
|
import android.icu.util.Calendar
|
||||||
import android.icu.util.TimeZone
|
import android.icu.util.TimeZone
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
import com.google.android.material.datepicker.MaterialDatePicker
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.slider.Slider
|
|
||||||
import com.google.android.material.timepicker.MaterialTimePicker
|
import com.google.android.material.timepicker.MaterialTimePicker
|
||||||
import com.google.android.material.timepicker.TimeFormat
|
import com.google.android.material.timepicker.TimeFormat
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
||||||
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.AbstractStringSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
|
|
||||||
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.*
|
||||||
|
import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
|
||||||
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
|
|
||||||
class SettingsAdapter(
|
class SettingsAdapter(
|
||||||
private val fragmentView: SettingsFragmentView,
|
private val fragment: Fragment,
|
||||||
private val context: Context
|
private val context: Context
|
||||||
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
|
) : ListAdapter<SettingsItem, SettingViewHolder>(
|
||||||
private var settings: ArrayList<SettingsItem>? = null
|
AsyncDifferConfig.Builder(DiffCallback()).build()
|
||||||
private var clickedItem: SettingsItem? = null
|
) {
|
||||||
private var clickedPosition: Int
|
private val settingsViewModel: SettingsViewModel
|
||||||
private var dialog: AlertDialog? = null
|
get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
|
||||||
private var sliderProgress = 0
|
|
||||||
private var textSliderValue: TextView? = null
|
|
||||||
|
|
||||||
private var defaultCancelListener =
|
|
||||||
DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
clickedPosition = -1
|
fragment.viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
settingsViewModel.adapterItemChanged.collect {
|
||||||
|
if (it != -1) {
|
||||||
|
notifyItemChanged(it)
|
||||||
|
settingsViewModel.setAdapterItemChanged(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
|
||||||
@ -90,67 +93,41 @@ class SettingsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
|
||||||
holder.bind(getItem(position))
|
holder.bind(currentList[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getItem(position: Int): SettingsItem {
|
override fun getItemCount(): Int = currentList.size
|
||||||
return settings!![position]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return if (settings != null) {
|
|
||||||
settings!!.size
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return getItem(position).type
|
return currentList[position].type
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSettingsList(settings: ArrayList<SettingsItem>?) {
|
fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
|
||||||
this.settings = settings
|
item.checked = checked
|
||||||
notifyDataSetChanged()
|
settingsViewModel.setShouldReloadSettingsList(true)
|
||||||
}
|
settingsViewModel.shouldSave = true
|
||||||
|
|
||||||
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
|
|
||||||
val setting = item.setChecked(checked)
|
|
||||||
fragmentView.putSetting(setting)
|
|
||||||
fragmentView.onSettingChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSingleChoiceClick(item: SingleChoiceSetting) {
|
|
||||||
clickedItem = item
|
|
||||||
val value = getSelectionForSingleChoiceValue(item)
|
|
||||||
dialog = MaterialAlertDialogBuilder(context)
|
|
||||||
.setTitle(item.nameId)
|
|
||||||
.setSingleChoiceItems(item.choicesId, value, this)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
|
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
|
||||||
clickedPosition = position
|
SettingsDialogFragment.newInstance(
|
||||||
onSingleChoiceClick(item)
|
settingsViewModel,
|
||||||
}
|
item,
|
||||||
|
SettingsItem.TYPE_SINGLE_CHOICE,
|
||||||
private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
|
position
|
||||||
clickedItem = item
|
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||||
dialog = MaterialAlertDialogBuilder(context)
|
|
||||||
.setTitle(item.nameId)
|
|
||||||
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
|
fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
|
||||||
clickedPosition = position
|
SettingsDialogFragment.newInstance(
|
||||||
onStringSingleChoiceClick(item)
|
settingsViewModel,
|
||||||
|
item,
|
||||||
|
SettingsItem.TYPE_STRING_SINGLE_CHOICE,
|
||||||
|
position
|
||||||
|
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
|
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
|
||||||
clickedItem = item
|
val storedTime = item.value * 1000
|
||||||
clickedPosition = position
|
|
||||||
val storedTime = java.lang.Long.decode(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()
|
||||||
@ -158,7 +135,7 @@ class SettingsAdapter(
|
|||||||
calendar.timeZone = TimeZone.getTimeZone("UTC")
|
calendar.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
var timeFormat: Int = TimeFormat.CLOCK_12H
|
var timeFormat: Int = TimeFormat.CLOCK_12H
|
||||||
if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) {
|
if (DateFormat.is24HourFormat(context)) {
|
||||||
timeFormat = TimeFormat.CLOCK_24H
|
timeFormat = TimeFormat.CLOCK_24H
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +152,7 @@ class SettingsAdapter(
|
|||||||
|
|
||||||
datePicker.addOnPositiveButtonClickListener {
|
datePicker.addOnPositiveButtonClickListener {
|
||||||
timePicker.show(
|
timePicker.show(
|
||||||
(fragmentView.activityView as AppCompatActivity).supportFragmentManager,
|
fragment.childFragmentManager,
|
||||||
"TimePicker"
|
"TimePicker"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -183,160 +160,50 @@ 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) {
|
settingsViewModel.shouldSave = true
|
||||||
fragmentView.onSettingChanged()
|
notifyItemChanged(position)
|
||||||
|
item.value = epochTime
|
||||||
}
|
}
|
||||||
notifyItemChanged(clickedPosition)
|
|
||||||
val setting = item.setSelectedValue(rtcString)
|
|
||||||
fragmentView.putSetting(setting)
|
|
||||||
clickedItem = null
|
|
||||||
}
|
}
|
||||||
datePicker.show(
|
datePicker.show(
|
||||||
(fragmentView.activityView as AppCompatActivity).supportFragmentManager,
|
fragment.childFragmentManager,
|
||||||
"DatePicker"
|
"DatePicker"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSliderClick(item: SliderSetting, position: Int) {
|
fun onSliderClick(item: SliderSetting, position: Int) {
|
||||||
clickedItem = item
|
SettingsDialogFragment.newInstance(
|
||||||
clickedPosition = position
|
settingsViewModel,
|
||||||
sliderProgress = item.selectedValue
|
item,
|
||||||
|
SettingsItem.TYPE_SLIDER,
|
||||||
val inflater = LayoutInflater.from(context)
|
position
|
||||||
val sliderBinding = DialogSliderBinding.inflate(inflater)
|
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||||
|
|
||||||
textSliderValue = sliderBinding.textValue
|
|
||||||
textSliderValue!!.text = String.format(
|
|
||||||
context.getString(R.string.value_with_units),
|
|
||||||
sliderProgress.toString(),
|
|
||||||
item.units
|
|
||||||
)
|
|
||||||
|
|
||||||
sliderBinding.slider.apply {
|
|
||||||
valueFrom = item.min.toFloat()
|
|
||||||
valueTo = item.max.toFloat()
|
|
||||||
value = sliderProgress.toFloat()
|
|
||||||
addOnChangeListener { _: Slider, value: Float, _: Boolean ->
|
|
||||||
sliderProgress = value.toInt()
|
|
||||||
textSliderValue!!.text = String.format(
|
|
||||||
context.getString(R.string.value_with_units),
|
|
||||||
sliderProgress.toString(),
|
|
||||||
item.units
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog = MaterialAlertDialogBuilder(context)
|
|
||||||
.setTitle(item.nameId)
|
|
||||||
.setView(sliderBinding.root)
|
|
||||||
.setPositiveButton(android.R.string.ok, this)
|
|
||||||
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSubmenuClick(item: SubmenuSetting) {
|
fun onSubmenuClick(item: SubmenuSetting) {
|
||||||
fragmentView.loadSubMenu(item.menuKey)
|
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
|
||||||
|
fragment.view?.findNavController()?.navigate(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
fun onLongClick(item: SettingsItem, position: Int): Boolean {
|
||||||
when (clickedItem) {
|
SettingsDialogFragment.newInstance(
|
||||||
is SingleChoiceSetting -> {
|
settingsViewModel,
|
||||||
val scSetting = clickedItem as SingleChoiceSetting
|
item,
|
||||||
val value = getValueForSingleChoiceSelection(scSetting, which)
|
SettingsDialogFragment.TYPE_RESET_SETTING,
|
||||||
if (scSetting.selectedValue != value) {
|
position
|
||||||
fragmentView.onSettingChanged()
|
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
closeDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
is StringSingleChoiceSetting -> {
|
|
||||||
val scSetting = clickedItem as StringSingleChoiceSetting
|
|
||||||
val value = scSetting.getValueAt(which)
|
|
||||||
if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
|
|
||||||
val setting = scSetting.setSelectedValue(value!!)
|
|
||||||
fragmentView.putSetting(setting)
|
|
||||||
closeDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
is SliderSetting -> {
|
|
||||||
val sliderSetting = clickedItem as SliderSetting
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
closeDialog()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clickedItem = null
|
|
||||||
sliderProgress = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
notifyItemChanged(position)
|
|
||||||
fragmentView.onSettingChanged()
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun closeDialog() {
|
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
|
||||||
if (dialog != null) {
|
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
|
||||||
if (clickedPosition != -1) {
|
return oldItem.setting.key == newItem.setting.key
|
||||||
notifyItemChanged(clickedPosition)
|
|
||||||
clickedPosition = -1
|
|
||||||
}
|
|
||||||
dialog!!.dismiss()
|
|
||||||
dialog = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
|
override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
|
||||||
val valuesId = item.valuesId
|
return oldItem.setting.key == newItem.setting.key
|
||||||
return if (valuesId > 0) {
|
|
||||||
val valuesArray = context.resources.getIntArray(valuesId)
|
|
||||||
valuesArray[which]
|
|
||||||
} else {
|
|
||||||
which
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
|
|
||||||
val value = item.selectedValue
|
|
||||||
val valuesId = item.valuesId
|
|
||||||
if (valuesId > 0) {
|
|
||||||
val valuesArray = context.resources.getIntArray(valuesId)
|
|
||||||
for (index in valuesArray.indices) {
|
|
||||||
val current = valuesArray[index]
|
|
||||||
if (current == value) {
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,40 +3,43 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.features.settings.ui
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.navigation.fragment.navArgs
|
||||||
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 com.google.android.material.transition.MaterialSharedAxis
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
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.utils.SettingsFile
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
|
|
||||||
class SettingsFragment : Fragment(), SettingsFragmentView {
|
class SettingsFragment : Fragment() {
|
||||||
override var activityView: SettingsActivityView? = null
|
private lateinit var presenter: SettingsFragmentPresenter
|
||||||
|
|
||||||
private val fragmentPresenter = SettingsFragmentPresenter(this)
|
|
||||||
private var settingsAdapter: SettingsAdapter? = null
|
private var settingsAdapter: SettingsAdapter? = null
|
||||||
|
|
||||||
private var _binding: FragmentSettingsBinding? = null
|
private var _binding: FragmentSettingsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
private val args by navArgs<SettingsFragmentArgs>()
|
||||||
super.onAttach(context)
|
|
||||||
activityView = requireActivity() as SettingsActivityView
|
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
|
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||||
val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
|
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||||
fragmentPresenter.onCreate(menuTag!!, gameId!!)
|
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||||
|
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@ -49,7 +52,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
settingsAdapter = SettingsAdapter(this, requireActivity())
|
settingsAdapter = SettingsAdapter(this, requireContext())
|
||||||
|
presenter = SettingsFragmentPresenter(
|
||||||
|
settingsViewModel,
|
||||||
|
settingsAdapter!!,
|
||||||
|
args.menuTag,
|
||||||
|
args.game?.gameId ?: ""
|
||||||
|
)
|
||||||
|
|
||||||
val dividerDecoration = MaterialDividerItemDecoration(
|
val dividerDecoration = MaterialDividerItemDecoration(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
LinearLayoutManager.VERTICAL
|
LinearLayoutManager.VERTICAL
|
||||||
@ -57,71 +67,86 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
|||||||
dividerDecoration.isLastItemDecorated = false
|
dividerDecoration.isLastItemDecorated = false
|
||||||
binding.listSettings.apply {
|
binding.listSettings.apply {
|
||||||
adapter = settingsAdapter
|
adapter = settingsAdapter
|
||||||
layoutManager = LinearLayoutManager(activity)
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
addItemDecoration(dividerDecoration)
|
addItemDecoration(dividerDecoration)
|
||||||
}
|
}
|
||||||
fragmentPresenter.onViewCreated()
|
|
||||||
|
binding.toolbarSettings.setNavigationOnClickListener {
|
||||||
|
settingsViewModel.setShouldNavigateBack(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
|
||||||
|
if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
|
||||||
|
if (it) {
|
||||||
|
settingsViewModel.setShouldReloadSettingsList(false)
|
||||||
|
presenter.loadSettingsList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
|
||||||
|
if (it) {
|
||||||
|
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||||
|
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||||
|
} else {
|
||||||
|
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||||
|
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
|
||||||
|
binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
|
||||||
|
binding.toolbarSettings.setOnMenuItemClickListener {
|
||||||
|
when (it.itemId) {
|
||||||
|
R.id.action_search -> {
|
||||||
|
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||||
|
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||||
|
view.findNavController()
|
||||||
|
.navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
presenter.onViewCreated()
|
||||||
|
|
||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetach() {
|
override fun onResume() {
|
||||||
super.onDetach()
|
super.onResume()
|
||||||
activityView = null
|
settingsViewModel.setIsUsingSearch(false)
|
||||||
if (settingsAdapter != null) {
|
|
||||||
settingsAdapter!!.closeDialog()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
|
|
||||||
settingsAdapter!!.setSettingsList(settingsList)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadSettingsList() {
|
|
||||||
fragmentPresenter.loadSettingsList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadSubMenu(menuKey: String) {
|
|
||||||
activityView!!.showSettingsFragment(
|
|
||||||
menuKey,
|
|
||||||
true,
|
|
||||||
requireArguments().getString(ARGUMENT_GAME_ID)!!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setInsets() {
|
private fun setInsets() {
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(
|
ViewCompat.setOnApplyWindowInsetsListener(
|
||||||
binding.listSettings
|
binding.root
|
||||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
view.updatePadding(bottom = insets.bottom)
|
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||||
|
|
||||||
|
val leftInsets = barInsets.left + cutoutInsets.left
|
||||||
|
val rightInsets = barInsets.right + cutoutInsets.right
|
||||||
|
|
||||||
|
val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
|
||||||
|
val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams
|
||||||
|
mlpSettingsList.leftMargin = sideMargin + leftInsets
|
||||||
|
mlpSettingsList.rightMargin = sideMargin + rightInsets
|
||||||
|
binding.listSettings.layoutParams = mlpSettingsList
|
||||||
|
binding.listSettings.updatePadding(
|
||||||
|
bottom = barInsets.bottom
|
||||||
|
)
|
||||||
|
|
||||||
|
val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
|
||||||
|
mlpAppBar.leftMargin = leftInsets
|
||||||
|
mlpAppBar.rightMargin = rightInsets
|
||||||
|
binding.appbarSettings.layoutParams = mlpAppBar
|
||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val ARGUMENT_MENU_TAG = "menu_tag"
|
|
||||||
private const val ARGUMENT_GAME_ID = "game_id"
|
|
||||||
|
|
||||||
fun newInstance(menuTag: String?, gameId: String?): Fragment {
|
|
||||||
val fragment = SettingsFragment()
|
|
||||||
val arguments = Bundle()
|
|
||||||
arguments.putString(ARGUMENT_MENU_TAG, menuTag)
|
|
||||||
arguments.putString(ARGUMENT_GAME_ID, gameId)
|
|
||||||
fragment.arguments = arguments
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,63 +3,66 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.features.settings.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
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.model.SettingsViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
|
class SettingsFragmentPresenter(
|
||||||
private var menuTag: String? = null
|
private val settingsViewModel: SettingsViewModel,
|
||||||
private lateinit var gameId: String
|
private val adapter: SettingsAdapter,
|
||||||
private var settingsList: ArrayList<SettingsItem>? = null
|
private var menuTag: String,
|
||||||
|
private var gameId: String
|
||||||
|
) {
|
||||||
|
private var settingsList = ArrayList<SettingsItem>()
|
||||||
|
|
||||||
private val settingsActivity get() = fragmentView.activityView as SettingsActivity
|
private val preferences: SharedPreferences
|
||||||
private val settings get() = fragmentView.activityView!!.settings
|
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
|
|
||||||
private lateinit var preferences: SharedPreferences
|
private val context: Context get() = YuzuApplication.appContext
|
||||||
|
|
||||||
fun onCreate(menuTag: String, gameId: String) {
|
// Extension for populating settings list based on paired settings
|
||||||
this.gameId = gameId
|
fun ArrayList<SettingsItem>.add(key: String) {
|
||||||
this.menuTag = menuTag
|
val item = SettingsItem.settingsItems[key]!!
|
||||||
|
val pairedSettingKey = item.setting.pairedSettingKey
|
||||||
|
if (pairedSettingKey.isNotEmpty()) {
|
||||||
|
val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
|
||||||
|
if (!pairedSettingValue) return
|
||||||
|
}
|
||||||
|
add(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewCreated() {
|
fun onViewCreated() {
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
|
||||||
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")
|
settingsViewModel.setToolbarTitle(
|
||||||
|
context.getString(
|
||||||
|
R.string.advanced_settings_game,
|
||||||
|
gameId
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sl = ArrayList<SettingsItem>()
|
val sl = ArrayList<SettingsItem>()
|
||||||
if (menuTag == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
when (menuTag) {
|
when (menuTag) {
|
||||||
SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
|
SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
|
||||||
Settings.SECTION_GENERAL -> addGeneralSettings(sl)
|
Settings.SECTION_GENERAL -> addGeneralSettings(sl)
|
||||||
@ -69,335 +72,104 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settingsList = sl
|
settingsList = sl
|
||||||
fragmentView.showSettingsList(settingsList!!)
|
adapter.submitList(settingsList)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings))
|
settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
|
||||||
sl.apply {
|
sl.apply {
|
||||||
|
add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
|
||||||
|
add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
|
||||||
|
add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
|
||||||
|
add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
|
||||||
|
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
|
||||||
add(
|
add(
|
||||||
SubmenuSetting(
|
RunnableSetting(R.string.reset_to_default, 0, false) {
|
||||||
R.string.preferences_general,
|
settingsViewModel.setShouldShowResetSettingsDialog(true)
|
||||||
0,
|
|
||||||
Settings.SECTION_GENERAL
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
R.string.preferences_system,
|
|
||||||
0,
|
|
||||||
Settings.SECTION_SYSTEM
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
R.string.preferences_graphics,
|
|
||||||
0,
|
|
||||||
Settings.SECTION_RENDERER
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
R.string.preferences_audio,
|
|
||||||
0,
|
|
||||||
Settings.SECTION_AUDIO
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
R.string.preferences_debug,
|
|
||||||
0,
|
|
||||||
Settings.SECTION_DEBUG
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
RunnableSetting(
|
|
||||||
R.string.reset_to_default,
|
|
||||||
0,
|
|
||||||
false
|
|
||||||
) {
|
|
||||||
ResetSettingsDialogFragment().show(
|
|
||||||
settingsActivity.supportFragmentManager,
|
|
||||||
ResetSettingsDialogFragment.TAG
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
|
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
|
||||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general))
|
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(
|
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
|
||||||
SwitchSetting(
|
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
|
||||||
IntSetting.RENDERER_USE_SPEED_LIMIT,
|
add(IntSetting.CPU_ACCURACY.key)
|
||||||
R.string.frame_limit_enable,
|
add(BooleanSetting.PICTURE_IN_PICTURE.key)
|
||||||
R.string.frame_limit_enable_description,
|
|
||||||
IntSetting.RENDERER_USE_SPEED_LIMIT.key,
|
|
||||||
IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SliderSetting(
|
|
||||||
IntSetting.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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.CPU_ACCURACY,
|
|
||||||
R.string.cpu_accuracy,
|
|
||||||
0,
|
|
||||||
R.array.cpuAccuracyNames,
|
|
||||||
R.array.cpuAccuracyValues,
|
|
||||||
IntSetting.CPU_ACCURACY.key,
|
|
||||||
IntSetting.CPU_ACCURACY.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SwitchSetting(
|
|
||||||
BooleanSetting.PICTURE_IN_PICTURE,
|
|
||||||
R.string.picture_in_picture,
|
|
||||||
R.string.picture_in_picture_description,
|
|
||||||
BooleanSetting.PICTURE_IN_PICTURE.key,
|
|
||||||
BooleanSetting.PICTURE_IN_PICTURE.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
|
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
|
||||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system))
|
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(
|
add(BooleanSetting.USE_DOCKED_MODE.key)
|
||||||
SwitchSetting(
|
add(IntSetting.REGION_INDEX.key)
|
||||||
IntSetting.USE_DOCKED_MODE,
|
add(IntSetting.LANGUAGE_INDEX.key)
|
||||||
R.string.use_docked_mode,
|
add(BooleanSetting.USE_CUSTOM_RTC.key)
|
||||||
R.string.use_docked_mode_description,
|
add(LongSetting.CUSTOM_RTC.key)
|
||||||
IntSetting.USE_DOCKED_MODE.key,
|
|
||||||
IntSetting.USE_DOCKED_MODE.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.REGION_INDEX,
|
|
||||||
R.string.emulated_region,
|
|
||||||
0,
|
|
||||||
R.array.regionNames,
|
|
||||||
R.array.regionValues,
|
|
||||||
IntSetting.REGION_INDEX.key,
|
|
||||||
IntSetting.REGION_INDEX.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.LANGUAGE_INDEX,
|
|
||||||
R.string.emulated_language,
|
|
||||||
0,
|
|
||||||
R.array.languageNames,
|
|
||||||
R.array.languageValues,
|
|
||||||
IntSetting.LANGUAGE_INDEX.key,
|
|
||||||
IntSetting.LANGUAGE_INDEX.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SwitchSetting(
|
|
||||||
BooleanSetting.USE_CUSTOM_RTC,
|
|
||||||
R.string.use_custom_rtc,
|
|
||||||
R.string.use_custom_rtc_description,
|
|
||||||
BooleanSetting.USE_CUSTOM_RTC.key,
|
|
||||||
BooleanSetting.USE_CUSTOM_RTC.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
DateTimeSetting(
|
|
||||||
StringSetting.CUSTOM_RTC,
|
|
||||||
R.string.set_custom_rtc,
|
|
||||||
0,
|
|
||||||
StringSetting.CUSTOM_RTC.key,
|
|
||||||
StringSetting.CUSTOM_RTC.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
||||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
|
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(
|
add(IntSetting.RENDERER_ACCURACY.key)
|
||||||
SingleChoiceSetting(
|
add(IntSetting.RENDERER_RESOLUTION.key)
|
||||||
IntSetting.RENDERER_ACCURACY,
|
add(IntSetting.RENDERER_VSYNC.key)
|
||||||
R.string.renderer_accuracy,
|
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||||
0,
|
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
||||||
R.array.rendererAccuracyNames,
|
add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
|
||||||
R.array.rendererAccuracyValues,
|
add(IntSetting.RENDERER_ASPECT_RATIO.key)
|
||||||
IntSetting.RENDERER_ACCURACY.key,
|
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||||
IntSetting.RENDERER_ACCURACY.defaultValue
|
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||||
)
|
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||||
)
|
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
||||||
add(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.RENDERER_RESOLUTION,
|
|
||||||
R.string.renderer_resolution,
|
|
||||||
0,
|
|
||||||
R.array.rendererResolutionNames,
|
|
||||||
R.array.rendererResolutionValues,
|
|
||||||
IntSetting.RENDERER_RESOLUTION.key,
|
|
||||||
IntSetting.RENDERER_RESOLUTION.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.RENDERER_VSYNC,
|
|
||||||
R.string.renderer_vsync,
|
|
||||||
0,
|
|
||||||
R.array.rendererVSyncNames,
|
|
||||||
R.array.rendererVSyncValues,
|
|
||||||
IntSetting.RENDERER_VSYNC.key,
|
|
||||||
IntSetting.RENDERER_VSYNC.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.RENDERER_SCALING_FILTER,
|
|
||||||
R.string.renderer_scaling_filter,
|
|
||||||
0,
|
|
||||||
R.array.rendererScalingFilterNames,
|
|
||||||
R.array.rendererScalingFilterValues,
|
|
||||||
IntSetting.RENDERER_SCALING_FILTER.key,
|
|
||||||
IntSetting.RENDERER_SCALING_FILTER.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.RENDERER_ANTI_ALIASING,
|
|
||||||
R.string.renderer_anti_aliasing,
|
|
||||||
0,
|
|
||||||
R.array.rendererAntiAliasingNames,
|
|
||||||
R.array.rendererAntiAliasingValues,
|
|
||||||
IntSetting.RENDERER_ANTI_ALIASING.key,
|
|
||||||
IntSetting.RENDERER_ANTI_ALIASING.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
|
||||||
R.string.renderer_screen_layout,
|
|
||||||
0,
|
|
||||||
R.array.rendererScreenLayoutNames,
|
|
||||||
R.array.rendererScreenLayoutValues,
|
|
||||||
IntSetting.RENDERER_SCREEN_LAYOUT.key,
|
|
||||||
IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.RENDERER_ASPECT_RATIO,
|
|
||||||
R.string.renderer_aspect_ratio,
|
|
||||||
0,
|
|
||||||
R.array.rendererAspectRatioNames,
|
|
||||||
R.array.rendererAspectRatioValues,
|
|
||||||
IntSetting.RENDERER_ASPECT_RATIO.key,
|
|
||||||
IntSetting.RENDERER_ASPECT_RATIO.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SwitchSetting(
|
|
||||||
IntSetting.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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SwitchSetting(
|
|
||||||
IntSetting.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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SwitchSetting(
|
|
||||||
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
|
||||||
R.string.renderer_asynchronous_shaders,
|
|
||||||
R.string.renderer_asynchronous_shaders_description,
|
|
||||||
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
|
|
||||||
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SwitchSetting(
|
|
||||||
IntSetting.RENDERER_REACTIVE_FLUSHING,
|
|
||||||
R.string.renderer_reactive_flushing,
|
|
||||||
R.string.renderer_reactive_flushing_description,
|
|
||||||
IntSetting.RENDERER_REACTIVE_FLUSHING.key,
|
|
||||||
IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
|
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
|
||||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
|
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(
|
add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
|
||||||
StringSingleChoiceSetting(
|
add(ByteSetting.AUDIO_VOLUME.key)
|
||||||
StringSetting.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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SliderSetting(
|
|
||||||
IntSetting.AUDIO_VOLUME,
|
|
||||||
R.string.audio_volume,
|
|
||||||
R.string.audio_volume_description,
|
|
||||||
0,
|
|
||||||
100,
|
|
||||||
"%",
|
|
||||||
IntSetting.AUDIO_VOLUME.key,
|
|
||||||
IntSetting.AUDIO_VOLUME.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
|
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
|
||||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
|
settingsViewModel.setToolbarTitle(context.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) {
|
|
||||||
|
override fun setInt(value: Int) {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putInt(Settings.PREF_THEME, value)
|
.putInt(Settings.PREF_THEME, value)
|
||||||
.apply()
|
.apply()
|
||||||
settingsActivity.recreate()
|
settingsViewModel.setShouldRecreate(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val key: String = Settings.PREF_THEME
|
||||||
|
override val category = Settings.Category.UiGeneral
|
||||||
|
override val isRuntimeModifiable: Boolean = false
|
||||||
|
override val defaultValue: Int = 0
|
||||||
|
override fun reset() {
|
||||||
|
preferences.edit()
|
||||||
|
.putInt(Settings.PREF_THEME, defaultValue)
|
||||||
|
.apply()
|
||||||
}
|
}
|
||||||
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 defaultValue: Any = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
@ -423,20 +195,26 @@ 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) {
|
|
||||||
|
override fun setInt(value: Int) {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putInt(Settings.PREF_THEME_MODE, value)
|
.putInt(Settings.PREF_THEME_MODE, value)
|
||||||
.apply()
|
.apply()
|
||||||
ThemeHelper.setThemeMode(settingsActivity)
|
settingsViewModel.setShouldRecreate(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val key: String = Settings.PREF_THEME_MODE
|
||||||
|
override val category = Settings.Category.UiGeneral
|
||||||
|
override val isRuntimeModifiable: Boolean = false
|
||||||
|
override val defaultValue: Int = -1
|
||||||
|
override fun reset() {
|
||||||
|
preferences.edit()
|
||||||
|
.putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
|
||||||
|
.apply()
|
||||||
|
settingsViewModel.setShouldRecreate(true)
|
||||||
}
|
}
|
||||||
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 defaultValue: Any = -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add(
|
add(
|
||||||
@ -450,21 +228,26 @@ 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) {
|
|
||||||
|
override fun setBoolean(value: Boolean) {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
|
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
|
||||||
.apply()
|
.apply()
|
||||||
settingsActivity.recreate()
|
settingsViewModel.setShouldRecreate(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val key: String = Settings.PREF_BLACK_BACKGROUNDS
|
||||||
|
override val category = Settings.Category.UiGeneral
|
||||||
|
override val isRuntimeModifiable: Boolean = false
|
||||||
|
override val defaultValue: Boolean = false
|
||||||
|
override fun reset() {
|
||||||
|
preferences.edit()
|
||||||
|
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
|
||||||
|
.apply()
|
||||||
|
settingsViewModel.setShouldRecreate(true)
|
||||||
}
|
}
|
||||||
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 defaultValue: Any = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add(
|
add(
|
||||||
@ -478,62 +261,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
|
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
|
||||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
|
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(HeaderSetting(R.string.gpu))
|
add(HeaderSetting(R.string.gpu))
|
||||||
add(
|
add(IntSetting.RENDERER_BACKEND.key)
|
||||||
SingleChoiceSetting(
|
add(BooleanSetting.RENDERER_DEBUG.key)
|
||||||
IntSetting.RENDERER_BACKEND,
|
|
||||||
R.string.renderer_api,
|
|
||||||
0,
|
|
||||||
R.array.rendererApiNames,
|
|
||||||
R.array.rendererApiValues,
|
|
||||||
IntSetting.RENDERER_BACKEND.key,
|
|
||||||
IntSetting.RENDERER_BACKEND.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SwitchSetting(
|
|
||||||
IntSetting.RENDERER_DEBUG,
|
|
||||||
R.string.renderer_debug,
|
|
||||||
R.string.renderer_debug_description,
|
|
||||||
IntSetting.RENDERER_DEBUG.key,
|
|
||||||
IntSetting.RENDERER_DEBUG.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
add(HeaderSetting(R.string.cpu))
|
add(HeaderSetting(R.string.cpu))
|
||||||
add(
|
add(BooleanSetting.CPU_DEBUG_MODE.key)
|
||||||
SwitchSetting(
|
add(SettingsItem.FASTMEM_COMBINED)
|
||||||
BooleanSetting.CPU_DEBUG_MODE,
|
|
||||||
R.string.cpu_debug_mode,
|
|
||||||
R.string.cpu_debug_mode_description,
|
|
||||||
BooleanSetting.CPU_DEBUG_MODE.key,
|
|
||||||
BooleanSetting.CPU_DEBUG_MODE.defaultValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val fastmem = object : AbstractBooleanSetting {
|
|
||||||
override var boolean: Boolean
|
|
||||||
get() =
|
|
||||||
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
|
|
||||||
set(value) {
|
|
||||||
BooleanSetting.FASTMEM.boolean = value
|
|
||||||
BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
|
|
||||||
}
|
|
||||||
override val key: String? = null
|
|
||||||
override val section: String = Settings.SECTION_CPU
|
|
||||||
override val isRuntimeEditable: Boolean = false
|
|
||||||
override val valueAsString: String = ""
|
|
||||||
override val defaultValue: Any = true
|
|
||||||
}
|
|
||||||
add(
|
|
||||||
SwitchSetting(
|
|
||||||
fastmem,
|
|
||||||
R.string.fastmem,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstraction for a screen showing a list of settings. Instances of
|
|
||||||
* this type of view will each display a layer of the setting hierarchy.
|
|
||||||
*/
|
|
||||||
interface SettingsFragmentView {
|
|
||||||
/**
|
|
||||||
* Pass an ArrayList to the View so that it can be displayed on screen.
|
|
||||||
*
|
|
||||||
* @param settingsList The result of converting the HashMap to an ArrayList
|
|
||||||
*/
|
|
||||||
fun showSettingsList(settingsList: ArrayList<SettingsItem>)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instructs the Fragment to load the settings screen.
|
|
||||||
*/
|
|
||||||
fun loadSettingsList()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The Fragment's containing activity.
|
|
||||||
*/
|
|
||||||
val activityView: SettingsActivityView?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tell the Fragment to tell the containing Activity to show a new
|
|
||||||
* Fragment containing a submenu of settings.
|
|
||||||
*
|
|
||||||
* @param menuKey Identifier for the settings group that should be shown.
|
|
||||||
*/
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
fun onSettingChanged()
|
|
||||||
}
|
|
@ -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)
|
||||||
@ -46,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
if (setting.isEditable) {
|
if (setting.isEditable) {
|
||||||
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
|
return adapter.onLongClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (item is StringSingleChoiceSetting) {
|
} else if (item is StringSingleChoiceSetting) {
|
||||||
for (i in item.values!!.indices) {
|
for (i in item.values.indices) {
|
||||||
if (item.values[i] == item.selectedValue) {
|
if (item.values[i] == item.selectedValue) {
|
||||||
binding.textSettingValue.text = item.choices[i]
|
binding.textSettingValue.text = item.choices[i]
|
||||||
break
|
break
|
||||||
@ -66,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
|||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
if (setting.isEditable) {
|
if (setting.isEditable) {
|
||||||
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
|
return adapter.onLongClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
|||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
if (setting.isEditable) {
|
if (setting.isEditable) {
|
||||||
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
|
return adapter.onLongClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
|||||||
binding.textSettingDescription.text = ""
|
binding.textSettingDescription.text = ""
|
||||||
binding.textSettingDescription.visibility = View.GONE
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.switchWidget.setOnCheckedChangeListener(null)
|
||||||
|
binding.switchWidget.isChecked = setting.checked
|
||||||
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
|
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
|
||||||
adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
|
adapter.onBooleanClick(item, binding.switchWidget.isChecked)
|
||||||
}
|
}
|
||||||
binding.switchWidget.isChecked = setting.isChecked
|
|
||||||
|
|
||||||
setStyle(setting.isEditable, binding)
|
setStyle(setting.isEditable, binding)
|
||||||
}
|
}
|
||||||
@ -41,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
|||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
if (setting.isEditable) {
|
if (setting.isEditable) {
|
||||||
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
|
return adapter.onLongClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.window.layout.FoldingFeature
|
import androidx.window.layout.FoldingFeature
|
||||||
@ -38,6 +39,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
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
|
||||||
@ -46,7 +48,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
|
|||||||
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
||||||
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.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.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||||
@ -158,7 +159,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_settings -> {
|
R.id.menu_settings -> {
|
||||||
SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
|
null,
|
||||||
|
SettingsFile.FILE_NAME_CONFIG
|
||||||
|
)
|
||||||
|
binding.root.findNavController().navigate(action)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +235,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||||
DirectoryInitialization.start(requireContext())
|
DirectoryInitialization.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScreenLayout()
|
updateScreenLayout()
|
||||||
|
@ -25,17 +25,18 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
import org.yuzu.yuzu_emu.BuildConfig
|
import org.yuzu.yuzu_emu.BuildConfig
|
||||||
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
||||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||||
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.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.model.HomeSetting
|
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
@ -74,7 +75,13 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
R.string.advanced_settings,
|
R.string.advanced_settings,
|
||||||
R.string.settings_description,
|
R.string.settings_description,
|
||||||
R.drawable.ic_settings,
|
R.drawable.ic_settings,
|
||||||
{ SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
{
|
||||||
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
|
null,
|
||||||
|
SettingsFile.FILE_NAME_CONFIG
|
||||||
|
)
|
||||||
|
binding.root.findNavController().navigate(action)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
@ -90,7 +97,13 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
R.string.preferences_theme,
|
R.string.preferences_theme,
|
||||||
R.string.theme_and_color_description,
|
R.string.theme_and_color_description,
|
||||||
R.drawable.ic_palette,
|
R.drawable.ic_palette,
|
||||||
{ SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
|
{
|
||||||
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
|
null,
|
||||||
|
Settings.SECTION_THEME
|
||||||
|
)
|
||||||
|
binding.root.findNavController().navigate(action)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
|
@ -0,0 +1,235 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.slider.Slider
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||||
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
|
|
||||||
|
class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
|
||||||
|
private var type = 0
|
||||||
|
private var position = 0
|
||||||
|
|
||||||
|
private var defaultCancelListener =
|
||||||
|
DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
|
||||||
|
|
||||||
|
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
||||||
|
|
||||||
|
private lateinit var sliderBinding: DialogSliderBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
type = requireArguments().getInt(TYPE)
|
||||||
|
position = requireArguments().getInt(POSITION)
|
||||||
|
|
||||||
|
if (settingsViewModel.clickedItem == null) dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
return when (type) {
|
||||||
|
TYPE_RESET_SETTING -> {
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setMessage(R.string.reset_setting_confirmation)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
|
settingsViewModel.clickedItem!!.setting.reset()
|
||||||
|
settingsViewModel.setAdapterItemChanged(position)
|
||||||
|
settingsViewModel.shouldSave = true
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsItem.TYPE_SINGLE_CHOICE -> {
|
||||||
|
val item = settingsViewModel.clickedItem as SingleChoiceSetting
|
||||||
|
val value = getSelectionForSingleChoiceValue(item)
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(item.nameId)
|
||||||
|
.setSingleChoiceItems(item.choicesId, value, this)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsItem.TYPE_SLIDER -> {
|
||||||
|
sliderBinding = DialogSliderBinding.inflate(layoutInflater)
|
||||||
|
val item = settingsViewModel.clickedItem as SliderSetting
|
||||||
|
|
||||||
|
settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units)
|
||||||
|
sliderBinding.slider.apply {
|
||||||
|
valueFrom = item.min.toFloat()
|
||||||
|
valueTo = item.max.toFloat()
|
||||||
|
value = settingsViewModel.sliderProgress.value.toFloat()
|
||||||
|
addOnChangeListener { _: Slider, value: Float, _: Boolean ->
|
||||||
|
settingsViewModel.setSliderTextValue(value, item.units)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(item.nameId)
|
||||||
|
.setView(sliderBinding.root)
|
||||||
|
.setPositiveButton(android.R.string.ok, this)
|
||||||
|
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
||||||
|
val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(item.nameId)
|
||||||
|
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.onCreateDialog(savedInstanceState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return when (type) {
|
||||||
|
SettingsItem.TYPE_SLIDER -> sliderBinding.root
|
||||||
|
else -> super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
when (type) {
|
||||||
|
SettingsItem.TYPE_SLIDER -> {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
settingsViewModel.sliderTextValue.collect {
|
||||||
|
sliderBinding.textValue.text = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
settingsViewModel.sliderProgress.collect {
|
||||||
|
sliderBinding.slider.value = it.toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||||
|
when (settingsViewModel.clickedItem) {
|
||||||
|
is SingleChoiceSetting -> {
|
||||||
|
val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
|
||||||
|
val value = getValueForSingleChoiceSelection(scSetting, which)
|
||||||
|
if (scSetting.selectedValue != value) {
|
||||||
|
settingsViewModel.shouldSave = true
|
||||||
|
}
|
||||||
|
scSetting.selectedValue = value
|
||||||
|
}
|
||||||
|
|
||||||
|
is StringSingleChoiceSetting -> {
|
||||||
|
val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
|
||||||
|
val value = scSetting.getValueAt(which)
|
||||||
|
if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
|
||||||
|
scSetting.selectedValue = value
|
||||||
|
}
|
||||||
|
|
||||||
|
is SliderSetting -> {
|
||||||
|
val sliderSetting = settingsViewModel.clickedItem as SliderSetting
|
||||||
|
if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
|
||||||
|
settingsViewModel.shouldSave = true
|
||||||
|
}
|
||||||
|
sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closeDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun closeDialog() {
|
||||||
|
settingsViewModel.setAdapterItemChanged(position)
|
||||||
|
settingsViewModel.clickedItem = null
|
||||||
|
settingsViewModel.setSliderProgress(-1f)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
|
||||||
|
val valuesId = item.valuesId
|
||||||
|
return if (valuesId > 0) {
|
||||||
|
val valuesArray = requireContext().resources.getIntArray(valuesId)
|
||||||
|
valuesArray[which]
|
||||||
|
} else {
|
||||||
|
which
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
|
||||||
|
val value = item.selectedValue
|
||||||
|
val valuesId = item.valuesId
|
||||||
|
if (valuesId > 0) {
|
||||||
|
val valuesArray = requireContext().resources.getIntArray(valuesId)
|
||||||
|
for (index in valuesArray.indices) {
|
||||||
|
val current = valuesArray[index]
|
||||||
|
if (current == value) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "SettingsDialogFragment"
|
||||||
|
|
||||||
|
const val TYPE_RESET_SETTING = -1
|
||||||
|
|
||||||
|
const val TITLE = "Title"
|
||||||
|
const val TYPE = "Type"
|
||||||
|
const val POSITION = "Position"
|
||||||
|
|
||||||
|
fun newInstance(
|
||||||
|
settingsViewModel: SettingsViewModel,
|
||||||
|
clickedItem: SettingsItem,
|
||||||
|
type: Int,
|
||||||
|
position: Int
|
||||||
|
): SettingsDialogFragment {
|
||||||
|
when (type) {
|
||||||
|
SettingsItem.TYPE_HEADER,
|
||||||
|
SettingsItem.TYPE_SWITCH,
|
||||||
|
SettingsItem.TYPE_SUBMENU,
|
||||||
|
SettingsItem.TYPE_DATETIME_SETTING,
|
||||||
|
SettingsItem.TYPE_RUNNABLE ->
|
||||||
|
throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
|
||||||
|
|
||||||
|
SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
|
||||||
|
(clickedItem as SliderSetting).selectedValue.toFloat()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
settingsViewModel.clickedItem = clickedItem
|
||||||
|
|
||||||
|
val args = Bundle()
|
||||||
|
args.putInt(TYPE, type)
|
||||||
|
args.putInt(POSITION, position)
|
||||||
|
val fragment = SettingsDialogFragment()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
|
import androidx.core.widget.doOnTextChanged
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||||
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
|
import info.debatty.java.stringsimilarity.Cosine
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
|
class SettingsSearchFragment : Fragment() {
|
||||||
|
private var _binding: FragmentSettingsSearchBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private var settingsAdapter: SettingsAdapter? = null
|
||||||
|
|
||||||
|
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||||
|
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
settingsViewModel.setIsUsingSearch(true)
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsAdapter = SettingsAdapter(this, requireContext())
|
||||||
|
|
||||||
|
val dividerDecoration = MaterialDividerItemDecoration(
|
||||||
|
requireContext(),
|
||||||
|
LinearLayoutManager.VERTICAL
|
||||||
|
)
|
||||||
|
dividerDecoration.isLastItemDecorated = false
|
||||||
|
binding.settingsList.apply {
|
||||||
|
adapter = settingsAdapter
|
||||||
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
addItemDecoration(dividerDecoration)
|
||||||
|
}
|
||||||
|
|
||||||
|
focusSearch()
|
||||||
|
|
||||||
|
binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
|
||||||
|
binding.searchBackground.setOnClickListener { focusSearch() }
|
||||||
|
binding.clearButton.setOnClickListener { binding.searchText.setText("") }
|
||||||
|
binding.searchText.doOnTextChanged { _, _, _, _ ->
|
||||||
|
search()
|
||||||
|
binding.settingsList.smoothScrollToPosition(0)
|
||||||
|
}
|
||||||
|
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
|
||||||
|
if (it) {
|
||||||
|
settingsViewModel.setShouldReloadSettingsList(false)
|
||||||
|
search()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
search()
|
||||||
|
|
||||||
|
setInsets()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun search() {
|
||||||
|
val searchTerm = binding.searchText.text.toString().lowercase()
|
||||||
|
binding.clearButton.visibility =
|
||||||
|
if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
|
||||||
|
if (searchTerm.isEmpty()) {
|
||||||
|
binding.noResultsView.visibility = View.VISIBLE
|
||||||
|
settingsAdapter?.submitList(emptyList())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val baseList = SettingsItem.settingsItems
|
||||||
|
val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
|
||||||
|
val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
|
||||||
|
val title = getString(item.value.nameId).lowercase()
|
||||||
|
val similarity = similarityAlgorithm.similarity(searchTerm, title)
|
||||||
|
if (similarity > 0.08) {
|
||||||
|
Pair(similarity, item)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.sortedByDescending { it.first }.mapNotNull {
|
||||||
|
val item = it.second.value
|
||||||
|
val pairedSettingKey = item.setting.pairedSettingKey
|
||||||
|
val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
|
||||||
|
val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
|
||||||
|
if (pairedSettingValue) it.second.value else null
|
||||||
|
} else {
|
||||||
|
it.second.value
|
||||||
|
}
|
||||||
|
optionalSetting
|
||||||
|
}
|
||||||
|
settingsAdapter?.submitList(sortedList)
|
||||||
|
binding.noResultsView.visibility =
|
||||||
|
if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun focusSearch() {
|
||||||
|
binding.searchText.requestFocus()
|
||||||
|
val imm = requireActivity()
|
||||||
|
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
|
||||||
|
imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setInsets() =
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(
|
||||||
|
binding.root
|
||||||
|
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||||
|
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
|
||||||
|
val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
|
||||||
|
val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
|
||||||
|
|
||||||
|
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||||
|
|
||||||
|
val leftInsets = barInsets.left + cutoutInsets.left
|
||||||
|
val rightInsets = barInsets.right + cutoutInsets.right
|
||||||
|
|
||||||
|
binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
|
||||||
|
binding.frameSearch.updatePadding(
|
||||||
|
left = leftInsets + sideMargin,
|
||||||
|
top = barInsets.top + topMargin,
|
||||||
|
right = rightInsets + sideMargin
|
||||||
|
)
|
||||||
|
binding.noResultsView.updatePadding(
|
||||||
|
left = leftInsets,
|
||||||
|
right = rightInsets,
|
||||||
|
bottom = barInsets.bottom
|
||||||
|
)
|
||||||
|
|
||||||
|
val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
|
mlpSettingsList.leftMargin = leftInsets + sideMargin
|
||||||
|
mlpSettingsList.rightMargin = rightInsets + sideMargin
|
||||||
|
binding.settingsList.layoutParams = mlpSettingsList
|
||||||
|
|
||||||
|
val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
|
mlpDivider.leftMargin = leftInsets + sideMargin
|
||||||
|
mlpDivider.rightMargin = rightInsets + sideMargin
|
||||||
|
binding.divider.layoutParams = mlpDivider
|
||||||
|
|
||||||
|
windowInsets
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SEARCH_TEXT = "SearchText"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.model
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
|
|
||||||
|
class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
|
||||||
|
var game: Game? = null
|
||||||
|
|
||||||
|
var shouldSave = false
|
||||||
|
|
||||||
|
var clickedItem: SettingsItem? = null
|
||||||
|
|
||||||
|
private val _toolbarTitle = MutableLiveData("")
|
||||||
|
val toolbarTitle: LiveData<String> get() = _toolbarTitle
|
||||||
|
|
||||||
|
private val _shouldRecreate = MutableLiveData(false)
|
||||||
|
val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
|
||||||
|
|
||||||
|
private val _shouldNavigateBack = MutableLiveData(false)
|
||||||
|
val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
|
||||||
|
|
||||||
|
private val _shouldShowResetSettingsDialog = MutableLiveData(false)
|
||||||
|
val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
|
||||||
|
|
||||||
|
private val _shouldReloadSettingsList = MutableLiveData(false)
|
||||||
|
val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
|
||||||
|
|
||||||
|
private val _isUsingSearch = MutableLiveData(false)
|
||||||
|
val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
|
||||||
|
|
||||||
|
val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
|
||||||
|
|
||||||
|
val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
|
||||||
|
|
||||||
|
val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
|
||||||
|
|
||||||
|
fun setToolbarTitle(value: String) {
|
||||||
|
_toolbarTitle.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShouldRecreate(value: Boolean) {
|
||||||
|
_shouldRecreate.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShouldNavigateBack(value: Boolean) {
|
||||||
|
_shouldNavigateBack.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShouldShowResetSettingsDialog(value: Boolean) {
|
||||||
|
_shouldShowResetSettingsDialog.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShouldReloadSettingsList(value: Boolean) {
|
||||||
|
_shouldReloadSettingsList.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setIsUsingSearch(value: Boolean) {
|
||||||
|
_isUsingSearch.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSliderTextValue(value: Float, units: String) {
|
||||||
|
savedStateHandle[KEY_SLIDER_PROGRESS] = value
|
||||||
|
savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
|
||||||
|
YuzuApplication.appContext.getString(R.string.value_with_units),
|
||||||
|
value.toInt().toString(),
|
||||||
|
units
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSliderProgress(value: Float) {
|
||||||
|
savedStateHandle[KEY_SLIDER_PROGRESS] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAdapterItemChanged(value: Int) {
|
||||||
|
savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
game = null
|
||||||
|
shouldSave = false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
|
||||||
|
const val KEY_SLIDER_PROGRESS = "SliderProgress"
|
||||||
|
const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
|
||||||
|
}
|
||||||
|
}
|
@ -33,14 +33,13 @@ import java.io.IOException
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
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.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
|
||||||
import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
|
import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
|
||||||
@ -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)
|
||||||
@ -109,11 +105,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
|
R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
|
||||||
R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
|
R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
|
||||||
R.id.homeSettingsFragment -> SettingsActivity.launch(
|
R.id.homeSettingsFragment -> {
|
||||||
this,
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
SettingsFile.FILE_NAME_CONFIG,
|
null,
|
||||||
""
|
SettingsFile.FILE_NAME_CONFIG
|
||||||
)
|
)
|
||||||
|
navHostFragment.navController.navigate(action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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]
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,18 +3,18 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
|
|
||||||
object DirectoryInitialization {
|
object DirectoryInitialization {
|
||||||
private var userPath: String? = null
|
private var userPath: String? = null
|
||||||
|
|
||||||
var areDirectoriesReady: Boolean = false
|
var areDirectoriesReady: Boolean = false
|
||||||
|
|
||||||
fun start(context: Context) {
|
fun start() {
|
||||||
if (!areDirectoriesReady) {
|
if (!areDirectoriesReady) {
|
||||||
initializeInternalStorage(context)
|
initializeInternalStorage()
|
||||||
NativeLibrary.initializeEmulation()
|
NativeLibrary.initializeEmulation()
|
||||||
areDirectoriesReady = true
|
areDirectoriesReady = true
|
||||||
}
|
}
|
||||||
@ -26,9 +26,9 @@ object DirectoryInitialization {
|
|||||||
return userPath
|
return userPath
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeInternalStorage(context: Context) {
|
private fun initializeInternalStorage() {
|
||||||
try {
|
try {
|
||||||
userPath = context.getExternalFilesDir(null)!!.canonicalPath
|
userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath
|
||||||
NativeLibrary.setAppDirectory(userPath!!)
|
NativeLibrary.setAppDirectory(userPath!!)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
external fun getPairedSettingKey(key: String): String
|
||||||
|
}
|
@ -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})
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
237
src/android/app/src/main/jni/native_config.cpp
Normal file
237
src/android/app/src/main/jni/native_config.cpp
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj,
|
||||||
|
jstring jkey) {
|
||||||
|
auto setting = getSetting<std::string>(env, jkey);
|
||||||
|
if (setting == nullptr) {
|
||||||
|
return ToJString(env, "");
|
||||||
|
}
|
||||||
|
if (setting->PairedSetting() == nullptr) {
|
||||||
|
return ToJString(env, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToJString(env, setting->PairedSetting()->GetLabel());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // 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
|
@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="125"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="1"
|
|
||||||
android:toAlpha="0" />
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:duration="125"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromXDelta="0"
|
|
||||||
android:toXDelta="-75" />
|
|
||||||
|
|
||||||
</set>
|
|
@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="0"
|
|
||||||
android:toAlpha="1" />
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromXDelta="-200"
|
|
||||||
android:toXDelta="0" />
|
|
||||||
|
|
||||||
</set>
|
|
@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="125"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="1"
|
|
||||||
android:toAlpha="0" />
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:duration="125"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromXDelta="0"
|
|
||||||
android:toXDelta="75" />
|
|
||||||
|
|
||||||
</set>
|
|
@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="0"
|
|
||||||
android:toAlpha="1" />
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromXDelta="200"
|
|
||||||
android:toXDelta="0" />
|
|
||||||
|
|
||||||
</set>
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:fromAlpha="1"
|
|
||||||
android:toAlpha="0" />
|
|
||||||
|
|
||||||
</set>
|
|
@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="translationX"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:valueFrom="-1280dp"
|
|
||||||
android:valueTo="0"
|
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
|
||||||
android:duration="300"/>
|
|
||||||
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="alpha"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="1"
|
|
||||||
android:interpolator="@android:interpolator/accelerate_quad"
|
|
||||||
android:duration="300"/>
|
|
||||||
|
|
||||||
</set>
|
|
@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<!-- This animation is used ONLY when a submenu is replaced. -->
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="translationX"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="-1280dp"
|
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
|
||||||
android:duration="200"/>
|
|
||||||
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="alpha"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:valueFrom="1"
|
|
||||||
android:valueTo="0"
|
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
|
||||||
android:duration="200"/>
|
|
||||||
|
|
||||||
</set>
|
|
@ -1,42 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/coordinator_main"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/constraint_settings"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/colorSurface">
|
android:background="?attr/colorSurface">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/appbar_settings"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:fitsSystemWindows="true"
|
android:layout_height="0dp"
|
||||||
app:elevation="0dp">
|
app:defaultNavHost="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
style="?attr/collapsingToolbarLayoutMediumStyle"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
android:id="@+id/toolbar_settings_layout"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_width="match_parent"
|
tools:layout="@layout/fragment_settings" />
|
||||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
|
||||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar_settings"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:layout_collapseMode="pin" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/frame_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginHorizontal="12dp"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/navigation_bar_shade"
|
android:id="@+id/navigation_bar_shade"
|
||||||
@ -45,6 +27,8 @@
|
|||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:layout_gravity="bottom|center_horizontal" />
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -1,14 +1,41 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/coordinator_main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/colorSurface">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar_settings"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
app:elevation="0dp">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
android:id="@+id/toolbar_settings_layout"
|
||||||
|
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar_settings"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:layout_collapseMode="pin"
|
||||||
|
app:navigationIcon="@drawable/ic_back" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/list_settings"
|
android:id="@+id/list_settings"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/colorSurface"
|
android:clipToPadding="false"
|
||||||
android:clipToPadding="false" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
</FrameLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
120
src/android/app/src/main/res/layout/fragment_settings_search.xml
Normal file
120
src/android/app/src/main/res/layout/fragment_settings_search.xml
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/relativeLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/divider">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/no_results_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_no_results"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:src="@drawable/ic_search" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/notice_text"
|
||||||
|
style="@style/TextAppearance.Material3.TitleLarge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:text="@string/search_settings"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/settings_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/frame_search"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/search_background"
|
||||||
|
style="?attr/materialCardViewFilledStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
app:cardCornerRadius="28dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/search_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="56dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/back_button"
|
||||||
|
style="?attr/materialIconButtonFilledTonalStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
app:backgroundTint="@android:color/transparent"
|
||||||
|
app:icon="@drawable/ic_back" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/search_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:hint="@string/search_settings"
|
||||||
|
android:imeOptions="flagNoFullscreen"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/clear_button"
|
||||||
|
style="?attr/materialIconButtonFilledTonalStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:visibility="invisible"
|
||||||
|
app:backgroundTint="@android:color/transparent"
|
||||||
|
app:icon="@drawable/ic_clear"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/frame_search" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,2 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu />
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_search"
|
||||||
|
android:icon="@drawable/ic_search"
|
||||||
|
android:title="@string/home_search"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
|
@ -17,4 +17,21 @@
|
|||||||
android:defaultValue="@null" />
|
android:defaultValue="@null" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:id="@+id/settingsActivity"
|
||||||
|
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
|
||||||
|
android:label="SettingsActivity">
|
||||||
|
<argument
|
||||||
|
android:name="game"
|
||||||
|
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||||
|
app:nullable="true" />
|
||||||
|
<argument
|
||||||
|
android:name="menuTag"
|
||||||
|
app:argType="string" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_settingsActivity"
|
||||||
|
app:destination="@id/settingsActivity" />
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
|
@ -72,4 +72,21 @@
|
|||||||
app:destination="@id/emulationActivity"
|
app:destination="@id/emulationActivity"
|
||||||
app:launchSingleTop="true" />
|
app:launchSingleTop="true" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:id="@+id/settingsActivity"
|
||||||
|
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
|
||||||
|
android:label="SettingsActivity">
|
||||||
|
<argument
|
||||||
|
android:name="game"
|
||||||
|
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||||
|
app:nullable="true" />
|
||||||
|
<argument
|
||||||
|
android:name="menuTag"
|
||||||
|
app:argType="string" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_settingsActivity"
|
||||||
|
app:destination="@id/settingsActivity" />
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/settings_navigation"
|
||||||
|
app:startDestination="@id/settingsFragment">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/settingsFragment"
|
||||||
|
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment"
|
||||||
|
android:label="SettingsFragment">
|
||||||
|
<argument
|
||||||
|
android:name="menuTag"
|
||||||
|
app:argType="string" />
|
||||||
|
<argument
|
||||||
|
android:name="game"
|
||||||
|
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||||
|
app:nullable="true" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_settingsFragment_to_settingsSearchFragment"
|
||||||
|
app:destination="@id/settingsSearchFragment" />
|
||||||
|
</fragment>
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_settingsFragment"
|
||||||
|
app:destination="@id/settingsFragment" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/settingsSearchFragment"
|
||||||
|
android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
|
||||||
|
android:label="SettingsSearchFragment" />
|
||||||
|
|
||||||
|
</navigation>
|
@ -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>
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
<string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
|
<string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
|
||||||
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
|
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
|
||||||
<string name="home_search_games">Search games</string>
|
<string name="home_search_games">Search games</string>
|
||||||
|
<string name="search_settings">Search settings</string>
|
||||||
<string name="games_dir_selected">Games directory selected</string>
|
<string name="games_dir_selected">Games directory selected</string>
|
||||||
<string name="install_prod_keys">Install prod.keys</string>
|
<string name="install_prod_keys">Install prod.keys</string>
|
||||||
<string name="install_prod_keys_description">Required to decrypt retail games</string>
|
<string name="install_prod_keys_description">Required to decrypt retail games</string>
|
||||||
@ -74,6 +75,7 @@
|
|||||||
<string name="install_gpu_driver">Install GPU driver</string>
|
<string name="install_gpu_driver">Install GPU driver</string>
|
||||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||||
<string name="advanced_settings">Advanced settings</string>
|
<string name="advanced_settings">Advanced settings</string>
|
||||||
|
<string name="advanced_settings_game">Advanced settings: %1$s</string>
|
||||||
<string name="settings_description">Configure emulator settings</string>
|
<string name="settings_description">Configure emulator settings</string>
|
||||||
<string name="search_recently_played">Recently played</string>
|
<string name="search_recently_played">Recently played</string>
|
||||||
<string name="search_recently_added">Recently added</string>
|
<string name="search_recently_added">Recently added</string>
|
||||||
@ -200,6 +202,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>
|
||||||
|
@ -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:
|
||||||
|
@ -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++;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user