commit
44bce11853
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
class BiMap<K, V> {
|
||||
private val forward: MutableMap<K, V> = HashMap()
|
||||
private val backward: MutableMap<V, K> = HashMap()
|
||||
|
||||
@Synchronized
|
||||
fun add(key: K, value: V) {
|
||||
forward[key] = value
|
||||
backward[value] = key
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getForward(key: K): V? {
|
||||
return forward[key]
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getBackward(key: V): K? {
|
||||
return backward[key]
|
||||
}
|
||||
}
|
@ -0,0 +1,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
|
||||
}
|
@ -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"
|
@ -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
|
@ -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,14 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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_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
|
||||
android:id="@+id/list_settings"
|
||||
android:layout_width="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>
|
||||
|
@ -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"?>
|
||||
<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>
|
||||
|
@ -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>
|
Loading…
Reference in New Issue