Add per-game settings configuration functionality

This commit is contained in:
lynxnb 2023-02-23 00:48:28 +01:00 committed by Niccolò Betto
parent 0467614dc0
commit 1a11aaa651
10 changed files with 251 additions and 48 deletions

View File

@ -23,6 +23,7 @@ import com.google.android.material.snackbar.Snackbar
import emu.skyline.data.AppItem import emu.skyline.data.AppItem
import emu.skyline.databinding.AppDialogBinding import emu.skyline.databinding.AppDialogBinding
import emu.skyline.loader.LoaderResult import emu.skyline.loader.LoaderResult
import emu.skyline.settings.SettingsActivity
/** /**
* This dialog is used to show extra game metadata and provide extra options such as pinning the game to the home screen * This dialog is used to show extra game metadata and provide extra options such as pinning the game to the home screen
@ -75,6 +76,13 @@ class AppDialog : BottomSheetDialogFragment() {
startActivity(Intent(activity, EmulationActivity::class.java).apply { data = item.uri }) startActivity(Intent(activity, EmulationActivity::class.java).apply { data = item.uri })
} }
binding.gameSettings.isEnabled = item.loaderResult == LoaderResult.Success
binding.gameSettings.setOnClickListener {
startActivity(Intent(activity, SettingsActivity::class.java).apply {
putExtras(requireArguments())
})
}
val shortcutManager = requireActivity().getSystemService(ShortcutManager::class.java) val shortcutManager = requireActivity().getSystemService(ShortcutManager::class.java)
binding.gamePin.isEnabled = shortcutManager.isRequestPinShortcutSupported binding.gamePin.isEnabled = shortcutManager.isRequestPinShortcutSupported

View File

@ -333,8 +333,7 @@ class MainActivity : AppCompatActivity() {
super.onResume() super.onResume()
// Try to return to normal GPU clocks upon resuming back to main activity, to avoid GPU being stuck at max clocks after a crash // Try to return to normal GPU clocks upon resuming back to main activity, to avoid GPU being stuck at max clocks after a crash
if (EmulationSettings.global.forceMaxGpuClocks) GpuDriverHelper.forceMaxGpuClocks(false)
GpuDriverHelper.forceMaxGpuClocks(false)
var layoutTypeChanged = false var layoutTypeChanged = false
for (appViewItem in adapter.allItems.filterIsInstance(AppViewItem::class.java)) { for (appViewItem in adapter.allItems.filterIsInstance(AppViewItem::class.java)) {

View File

@ -22,6 +22,7 @@ import emu.skyline.adapter.GenericListItem
import emu.skyline.adapter.GpuDriverViewItem import emu.skyline.adapter.GpuDriverViewItem
import emu.skyline.adapter.SelectableGenericAdapter import emu.skyline.adapter.SelectableGenericAdapter
import emu.skyline.adapter.SpacingItemDecoration import emu.skyline.adapter.SpacingItemDecoration
import emu.skyline.data.AppItem
import emu.skyline.databinding.GpuDriverActivityBinding import emu.skyline.databinding.GpuDriverActivityBinding
import emu.skyline.settings.EmulationSettings import emu.skyline.settings.EmulationSettings
import emu.skyline.utils.GpuDriverHelper import emu.skyline.utils.GpuDriverHelper
@ -38,6 +39,8 @@ import kotlinx.coroutines.launch
class GpuDriverActivity : AppCompatActivity() { class GpuDriverActivity : AppCompatActivity() {
private val binding by lazy { GpuDriverActivityBinding.inflate(layoutInflater) } private val binding by lazy { GpuDriverActivityBinding.inflate(layoutInflater) }
private val item by lazy { intent.extras?.getSerializable("item") as AppItem? }
private val adapter = SelectableGenericAdapter(0) private val adapter = SelectableGenericAdapter(0)
lateinit var emulationSettings : EmulationSettings lateinit var emulationSettings : EmulationSettings
@ -80,7 +83,8 @@ class GpuDriverActivity : AppCompatActivity() {
GpuDriverHelper.getInstalledDrivers(this).onEachIndexed { index, (file, metadata) -> GpuDriverHelper.getInstalledDrivers(this).onEachIndexed { index, (file, metadata) ->
items.add(GpuDriverViewItem(metadata).apply { items.add(GpuDriverViewItem(metadata).apply {
onDelete = { position, wasChecked -> // Enable the delete button when configuring global settings only
onDelete = if (emulationSettings.isGlobal) { position, wasChecked ->
// If the driver was selected, select the system driver as the active one // If the driver was selected, select the system driver as the active one
if (wasChecked) if (wasChecked)
emulationSettings.gpuDriver = EmulationSettings.SYSTEM_GPU_DRIVER emulationSettings.gpuDriver = EmulationSettings.SYSTEM_GPU_DRIVER
@ -103,7 +107,7 @@ class GpuDriverActivity : AppCompatActivity() {
} }
} }
}).show() }).show()
} } else null
onClick = { onClick = {
emulationSettings.gpuDriver = metadata.label emulationSettings.gpuDriver = metadata.label
@ -127,8 +131,18 @@ class GpuDriverActivity : AppCompatActivity() {
WindowInsetsHelper.addMargin(binding.addDriverButton, bottom = true) WindowInsetsHelper.addMargin(binding.addDriverButton, bottom = true)
setSupportActionBar(binding.titlebar.toolbar) setSupportActionBar(binding.titlebar.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.apply {
supportActionBar?.title = getString(R.string.gpu_driver_config) setDisplayHomeAsUpEnabled(true)
title = getString(R.string.gpu_driver_config)
subtitle = item?.title
}
emulationSettings = if (item == null) {
EmulationSettings.global
} else {
val appItem = item as AppItem
EmulationSettings.forTitleId(appItem.titleId ?: appItem.key())
}
val layoutManager = LinearLayoutManager(this) val layoutManager = LinearLayoutManager(this)
binding.driverList.layoutManager = layoutManager binding.driverList.layoutManager = layoutManager
@ -173,7 +187,6 @@ class GpuDriverActivity : AppCompatActivity() {
installCallback.launch(intent) installCallback.launch(intent)
} }
emulationSettings = EmulationSettings.global
populateAdapter() populateAdapter()
} }

View File

@ -13,6 +13,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.Preference.SummaryProvider import androidx.preference.Preference.SummaryProvider
import androidx.preference.R import androidx.preference.R
import emu.skyline.data.AppItem
import emu.skyline.settings.EmulationSettings import emu.skyline.settings.EmulationSettings
import emu.skyline.utils.GpuDriverHelper import emu.skyline.utils.GpuDriverHelper
import emu.skyline.R as SkylineR import emu.skyline.R as SkylineR
@ -25,6 +26,12 @@ class GpuDriverPreference @JvmOverloads constructor(context : Context, attrs : A
notifyChanged() notifyChanged()
} }
/**
* The app item being configured, used to load the correct settings in [GpuDriverActivity]
* This is populated by [emu.skyline.settings.GameSettingsFragment]
*/
var item : AppItem? = null
init { init {
val supportsCustomDriverLoading = GpuDriverHelper.supportsCustomDriverLoading() val supportsCustomDriverLoading = GpuDriverHelper.supportsCustomDriverLoading()
if (supportsCustomDriverLoading) { if (supportsCustomDriverLoading) {
@ -48,5 +55,7 @@ class GpuDriverPreference @JvmOverloads constructor(context : Context, attrs : A
/** /**
* This launches [GpuDriverActivity] on click to manage driver packages * This launches [GpuDriverActivity] on click to manage driver packages
*/ */
override fun onClick() = driverCallback.launch(Intent(context, GpuDriverActivity::class.java)) override fun onClick() = driverCallback.launch(Intent(context, GpuDriverActivity::class.java).apply {
putExtra("item", item)
})
} }

View File

@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import androidx.preference.Preference
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import emu.skyline.R
/**
* Clears all values in the current shared preferences, showing a dialog to confirm the action
* This preference recreates the activity to update the UI after modifying shared preferences
*/
class ResetSettingsPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = androidx.preference.R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) {
init {
setOnPreferenceClickListener {
MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(R.string.reset_settings_warning)
.setPositiveButton(android.R.string.ok) { _, _ ->
// Clear all shared preferences
sharedPreferences?.apply { edit().clear().apply() }
// Recreate the activity to update the UI after modifying shared preferences
(context as? Activity)?.recreate()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
true
}
}
}

View File

@ -0,0 +1,93 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.settings
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.*
import emu.skyline.BuildConfig
import emu.skyline.R
import emu.skyline.data.AppItem
import emu.skyline.preference.GpuDriverPreference
import emu.skyline.preference.IntegerListPreference
import emu.skyline.utils.GpuDriverHelper
import emu.skyline.utils.WindowInsetsHelper
/**
* This fragment is used to display custom game preferences
*/
class GameSettingsFragment : PreferenceFragmentCompat() {
companion object {
private const val DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG"
}
private val item by lazy { requireArguments().getSerializable("item")!! as AppItem }
override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById<View>(R.id.recycler_view)
WindowInsetsHelper.setPadding(recyclerView, bottom = true)
(activity as AppCompatActivity).supportActionBar?.subtitle = item.title
}
/**
* This constructs the preferences from XML preference resources
*/
override fun onCreatePreferences(savedInstanceState : Bundle?, rootKey : String?) {
preferenceManager.sharedPreferencesName = EmulationSettings.prefNameForTitle(item.titleId ?: item.key())
addPreferencesFromResource(R.xml.custom_game_preferences)
addPreferencesFromResource(R.xml.emulation_preferences)
// Toggle emulation settings enabled state based on use_custom_settings state
listOf<Preference?>(
findPreference("category_system"),
findPreference("category_presentation"),
findPreference("category_gpu"),
findPreference("category_hacks"),
findPreference("category_audio"),
findPreference("category_debug")
).forEach { it?.dependency = "use_custom_settings" }
// Uncheck `disable_frame_throttling` if `force_triple_buffering` gets disabled
val disableFrameThrottlingPref = findPreference<CheckBoxPreference>("disable_frame_throttling")!!
findPreference<CheckBoxPreference>("force_triple_buffering")?.setOnPreferenceChangeListener { _, newValue ->
if (newValue == false)
disableFrameThrottlingPref.isChecked = false
true
}
// Only show debug settings in debug builds
@Suppress("SENSELESS_COMPARISON")
if (BuildConfig.BUILD_TYPE != "release")
findPreference<Preference>("category_debug")?.isVisible = true
if (!GpuDriverHelper.supportsForceMaxGpuClocks()) {
val forceMaxGpuClocksPref = findPreference<CheckBoxPreference>("force_max_gpu_clocks")!!
forceMaxGpuClocksPref.isSelectable = false
forceMaxGpuClocksPref.isChecked = false
forceMaxGpuClocksPref.summary = context!!.getString(R.string.force_max_gpu_clocks_desc_unsupported)
}
findPreference<GpuDriverPreference>("gpu_driver")?.item = item
}
override fun onDisplayPreferenceDialog(preference : Preference) {
if (preference is IntegerListPreference) {
// Check if dialog is already showing
if (parentFragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null)
return
val dialogFragment = IntegerListPreference.IntegerListPreferenceDialogFragmentCompat.newInstance(preference.getKey())
@Suppress("DEPRECATION")
dialogFragment.setTargetFragment(this, 0) // androidx.preference.PreferenceDialogFragmentCompat depends on the target fragment being set correctly even though it's deprecated
dialogFragment.show(parentFragmentManager, DIALOG_FRAGMENT_TAG)
} else {
super.onDisplayPreferenceDialog(preference)
}
}
}

View File

@ -25,8 +25,12 @@ class SettingsActivity : AppCompatActivity() {
/** /**
* The instance of [PreferenceFragmentCompat] that is shown inside [R.id.settings] * The instance of [PreferenceFragmentCompat] that is shown inside [R.id.settings]
* Retrieves extras from the intent if any and instantiates the appropriate fragment
*/ */
private val preferenceFragment by lazy { private val preferenceFragment by lazy {
if (intent.hasExtra("item"))
GameSettingsFragment().apply { arguments = intent.extras }
else
GlobalSettingsFragment() GlobalSettingsFragment()
} }

View File

@ -17,7 +17,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusRight="@id/game_play" android:nextFocusRight="@id/game_play"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
android:paddingBottom="16dp"> android:paddingBottom="8dp">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/game_icon" android:id="@+id/game_icon"
@ -36,9 +36,8 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
app:layout_constraintHeight_min="140dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_min="140dp"
app:layout_constraintStart_toEndOf="@id/game_icon" app:layout_constraintStart_toEndOf="@id/game_icon"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -86,43 +85,59 @@
app:layout_constraintTop_toBottomOf="@id/game_version" app:layout_constraintTop_toBottomOf="@id/game_version"
tools:text="Nintendo" /> tools:text="Nintendo" />
<com.google.android.flexbox.FlexboxLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
app:flexWrap="nowrap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/game_author"
app:layout_constraintVertical_bias="1">
<Button
android:id="@+id/game_play"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/play"
android:focusedByDefault="true"
android:text="@string/play"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_play"
app:iconTint="?attr/colorAccent"
app:layout_flexGrow="1"
app:layout_maxWidth="180dp" />
<Button
android:id="@+id/game_pin"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/pin"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_add_home"
app:iconGravity="textStart"
android:padding="0dp"
app:iconPadding="0dp" />
</com.google.android.flexbox.FlexboxLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.flexbox.FlexboxLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:flexWrap="nowrap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/game_icon"
app:layout_constraintVertical_bias="1">
<Button
android:id="@+id/game_play"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/play"
android:focusedByDefault="true"
android:text="@string/play"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_play"
app:iconTint="?attr/colorAccent"
app:layout_flexGrow="1"
app:layout_maxWidth="140dp" />
<Button
android:id="@+id/game_settings"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/settings"
android:padding="0dp"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_settings"
app:iconGravity="textStart"
app:iconPadding="0dp" />
<Button
android:id="@+id/game_pin"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/pin"
android:padding="0dp"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_add_home"
app:iconGravity="textStart"
app:iconPadding="0dp" />
</com.google.android.flexbox.FlexboxLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>

View File

@ -43,6 +43,14 @@
<string name="select_action">Always Show Game Information</string> <string name="select_action">Always Show Game Information</string>
<string name="select_action_desc_on">Game information will be shown on clicking a game</string> <string name="select_action_desc_on">Game information will be shown on clicking a game</string>
<string name="select_action_desc_off">Game information will only be shown on long-clicking a game</string> <string name="select_action_desc_off">Game information will only be shown on long-clicking a game</string>
<!-- Settings - Game -->
<string name="game">Game</string>
<string name="use_custom_settings">Enable Custom Settings</string>
<string name="use_custom_settings_desc_on">Custom settings are enabled for this game</string>
<string name="use_custom_settings_desc_off">Custom settings are disabled for this game</string>
<string name="reset_custom_settings">Reset Custom Settings</string>
<string name="reset_custom_settings_desc">Reset custom settings to the default values</string>
<string name="reset_settings_warning">Are you sure you want to reset all settings to the default values? <b>Current settings will be lost</b></string>
<!-- Settings - System --> <!-- Settings - System -->
<string name="system">System</string> <string name="system">System</string>
<string name="use_docked">Use Docked Mode</string> <string name="use_docked">Use Docked Mode</string>

View File

@ -0,0 +1,17 @@
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:key="category_game"
android:title="@string/game">
<CheckBoxPreference
android:defaultValue="false"
android:summaryOff="@string/use_custom_settings_desc_off"
android:summaryOn="@string/use_custom_settings_desc_on"
app:key="use_custom_settings"
app:title="@string/use_custom_settings" />
<emu.skyline.preference.ResetSettingsPreference
android:summary="@string/reset_custom_settings_desc"
app:key="reset_custom_settings"
app:title="@string/reset_custom_settings" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>