This commit is contained in:
David Griswold 2025-10-22 07:24:32 +02:00 committed by GitHub
commit ba507b7413
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 784 additions and 118 deletions

View file

@ -12,6 +12,7 @@ import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.EmulationMenuSettings
@ -31,8 +32,16 @@ class ScreenAdjustmentUtil(
BooleanSetting.SWAP_SCREEN.boolean = isEnabled BooleanSetting.SWAP_SCREEN.boolean = isEnabled
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
} }
fun cycleLayouts() { fun cycleLayouts() {
val landscapeValues = context.resources.getIntArray(R.array.landscapeValues)
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
val landscapeValues =
if (landscapeLayoutsToCycle.isNotEmpty())
landscapeLayoutsToCycle.toIntArray()
else context.resources.getIntArray(
R.array.landscapeValues
)
val portraitValues = context.resources.getIntArray(R.array.portraitValues) val portraitValues = context.resources.getIntArray(R.array.portraitValues)
if (NativeLibrary.isPortraitMode) { if (NativeLibrary.isPortraitMode) {

View file

@ -0,0 +1,9 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model
interface AbstractListSetting<E> : AbstractSetting {
var list: List<E>
}

View file

@ -0,0 +1,39 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model
enum class IntListSetting(
override val key: String,
override val section: String,
override val defaultValue: List<Int>
) : AbstractListSetting<Int> {
LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0,1,2,3,4,5));
override var list: List<Int> = defaultValue
override val valueAsString: String
get() = list.joinToString()
override val isRuntimeEditable: Boolean
get() {
for (setting in NOT_RUNTIME_EDITABLE) {
if (setting == this) {
return false
}
}
return true
}
companion object {
private val NOT_RUNTIME_EDITABLE:List<IntListSetting> = listOf();
fun from(key: String): IntListSetting? =
values().firstOrNull { it.key == key }
fun clear() = values().forEach { it.list = it.defaultValue }
}
}

View file

@ -0,0 +1,46 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
class MultiChoiceSetting(
setting: AbstractSetting?,
titleId: Int,
descriptionId: Int,
val choicesId: Int,
val valuesId: Int,
val key: String? = null,
val defaultValue: List<Int>? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_MULTI_CHOICE
val selectedValues: List<Int>
get() {
if (setting == null) {
return defaultValue!!
}
try {
val setting = setting as IntListSetting
return setting.list
}catch (_: ClassCastException) {
}
return defaultValue!!
}
/**
* Write a value to the backing list. 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: List<Int>): IntListSetting {
val intSetting = setting as IntListSetting
intSetting.list = selection
return intSetting
}
}

View file

@ -47,5 +47,6 @@ abstract class SettingsItem(
const val TYPE_INPUT_BINDING = 8 const val TYPE_INPUT_BINDING = 8
const val TYPE_STRING_INPUT = 9 const val TYPE_STRING_INPUT = 9
const val TYPE_FLOAT_INPUT = 10 const val TYPE_FLOAT_INPUT = 10
const val TYPE_MULTI_CHOICE = 11
} }
} }

View file

@ -41,12 +41,14 @@ import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.FloatSetting import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
import org.citra.citra_emu.features.settings.model.AbstractShortSetting import org.citra.citra_emu.features.settings.model.AbstractShortSetting
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.SliderSetting import org.citra.citra_emu.features.settings.model.view.SliderSetting
import org.citra.citra_emu.features.settings.model.view.StringInputSetting import org.citra.citra_emu.features.settings.model.view.StringInputSetting
import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting
@ -55,6 +57,7 @@ import org.citra.citra_emu.features.settings.model.view.SwitchSetting
import org.citra.citra_emu.features.settings.ui.viewholder.DateTimeViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.DateTimeViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.HeaderViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.HeaderViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.InputBindingSettingViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.InputBindingSettingViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.MultiChoiceViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.RunnableViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.RunnableViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.SettingViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.SettingViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.SingleChoiceViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.SingleChoiceViewHolder
@ -72,7 +75,8 @@ import kotlin.math.roundToInt
class SettingsAdapter( class SettingsAdapter(
private val fragmentView: SettingsFragmentView, private val fragmentView: SettingsFragmentView,
public val context: Context public val context: Context
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener { ) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
DialogInterface.OnMultiChoiceClickListener {
private var settings: ArrayList<SettingsItem>? = null private var settings: ArrayList<SettingsItem>? = null
private var clickedItem: SettingsItem? = null private var clickedItem: SettingsItem? = null
private var clickedPosition: Int private var clickedPosition: Int
@ -104,6 +108,10 @@ class SettingsAdapter(
SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this) SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
} }
SettingsItem.TYPE_MULTI_CHOICE -> {
MultiChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
SettingsItem.TYPE_SLIDER -> { SettingsItem.TYPE_SLIDER -> {
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this) SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
} }
@ -181,21 +189,30 @@ class SettingsAdapter(
SettingsItem.TYPE_SLIDER -> { SettingsItem.TYPE_SLIDER -> {
(oldItem as SliderSetting).isEnabled == (newItem as SliderSetting).isEnabled (oldItem as SliderSetting).isEnabled == (newItem as SliderSetting).isEnabled
} }
SettingsItem.TYPE_SWITCH -> { SettingsItem.TYPE_SWITCH -> {
(oldItem as SwitchSetting).isEnabled == (newItem as SwitchSetting).isEnabled (oldItem as SwitchSetting).isEnabled == (newItem as SwitchSetting).isEnabled
} }
SettingsItem.TYPE_SINGLE_CHOICE -> { SettingsItem.TYPE_SINGLE_CHOICE -> {
(oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled (oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled
} }
SettingsItem.TYPE_MULTI_CHOICE -> {
(oldItem as MultiChoiceSetting).isEnabled == (newItem as MultiChoiceSetting).isEnabled
}
SettingsItem.TYPE_DATETIME_SETTING -> { SettingsItem.TYPE_DATETIME_SETTING -> {
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled (oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
} }
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> { SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled (oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
} }
SettingsItem.TYPE_STRING_INPUT -> { SettingsItem.TYPE_STRING_INPUT -> {
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled (oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
} }
else -> { else -> {
oldItem == newItem oldItem == newItem
} }
@ -214,7 +231,7 @@ class SettingsAdapter(
// If statement is required otherwise the app will crash on activity recreate ex. theme settings // If statement is required otherwise the app will crash on activity recreate ex. theme settings
if (fragmentView.activityView != null) if (fragmentView.activityView != null)
// Reload the settings list to update the UI // Reload the settings list to update the UI
fragmentView.loadSettingsList() fragmentView.loadSettingsList()
} }
@ -232,6 +249,21 @@ class SettingsAdapter(
onSingleChoiceClick(item) onSingleChoiceClick(item)
} }
private fun onMultiChoiceClick(item: MultiChoiceSetting) {
clickedItem = item
val value: BooleanArray = getSelectionForMultiChoiceValue(item);
dialog = MaterialAlertDialogBuilder(context)
.setTitle(item.nameId)
.setMultiChoiceItems(item.choicesId, value, this)
.show()
}
fun onMultiChoiceClick(item: MultiChoiceSetting, position: Int) {
clickedPosition = position
onMultiChoiceClick(item)
}
private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) { private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
clickedItem = item clickedItem = item
dialog = context?.let { dialog = context?.let {
@ -360,14 +392,14 @@ class SettingsAdapter(
sliderString = sliderProgress.roundToInt().toString() sliderString = sliderProgress.roundToInt().toString()
if (textSliderValue?.text.toString() != sliderString) { if (textSliderValue?.text.toString() != sliderString) {
textSliderValue?.setText(sliderString) textSliderValue?.setText(sliderString)
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 ) textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
} }
} else { } else {
val currentText = textSliderValue?.text.toString() val currentText = textSliderValue?.text.toString()
val currentTextValue = currentText.toFloat() val currentTextValue = currentText.toFloat()
if (currentTextValue != sliderProgress) { if (currentTextValue != sliderProgress) {
textSliderValue?.setText(sliderString) textSliderValue?.setText(sliderString)
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 ) textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
} }
} }
} }
@ -447,6 +479,7 @@ class SettingsAdapter(
} }
it.setSelectedValue(value) it.setSelectedValue(value)
} }
is AbstractShortSetting -> { is AbstractShortSetting -> {
val value = getValueForSingleChoiceSelection(it, which).toShort() val value = getValueForSingleChoiceSelection(it, which).toShort()
if (it.selectedValue.toShort() != value) { if (it.selectedValue.toShort() != value) {
@ -454,6 +487,7 @@ class SettingsAdapter(
} }
it.setSelectedValue(value) it.setSelectedValue(value)
} }
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!") else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
} }
fragmentView?.putSetting(setting) fragmentView?.putSetting(setting)
@ -499,11 +533,12 @@ class SettingsAdapter(
val setting = it.setSelectedValue(value) val setting = it.setSelectedValue(value)
fragmentView?.putSetting(setting) fragmentView?.putSetting(setting)
} }
else -> { else -> {
val setting = it.setSelectedValue(sliderProgress) val setting = it.setSelectedValue(sliderProgress)
fragmentView?.putSetting(setting) fragmentView?.putSetting(setting)
} }
} }
fragmentView.loadSettingsList() fragmentView.loadSettingsList()
closeDialog() closeDialog()
} }
@ -519,7 +554,7 @@ class SettingsAdapter(
fragmentView?.putSetting(setting) fragmentView?.putSetting(setting)
fragmentView.loadSettingsList() fragmentView.loadSettingsList()
closeDialog() closeDialog()
} }
} }
} }
clickedItem = null clickedItem = null
@ -527,6 +562,21 @@ class SettingsAdapter(
textInputValue = "" textInputValue = ""
} }
//onclick for multichoice
override fun onClick(dialog: DialogInterface?, which: Int, isChecked: Boolean) {
val mcsetting = clickedItem as? MultiChoiceSetting
mcsetting?.let {
val value = getValueForMultiChoiceSelection(it, which)
if (it.selectedValues.contains(value) != isChecked) {
val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
fragmentView?.putSetting(setting)
fragmentView?.onSettingChanged()
}
fragmentView.loadSettingsList()
}
}
fun onLongClick(setting: AbstractSetting, position: Int): Boolean { fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
.setMessage(R.string.reset_setting_confirmation) .setMessage(R.string.reset_setting_confirmation)
@ -616,6 +666,16 @@ class SettingsAdapter(
} }
} }
private fun getValueForMultiChoiceSelection(item: MultiChoiceSetting, which: Int): Int {
val valuesId = item.valuesId
return if (valuesId > 0) {
val valuesArray = context.resources.getIntArray(valuesId)
valuesArray[which]
} else {
which
}
}
private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int { private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
val value = item.selectedValue val value = item.selectedValue
val valuesId = item.valuesId val valuesId = item.valuesId
@ -632,4 +692,20 @@ class SettingsAdapter(
} }
return -1 return -1
} }
private fun getSelectionForMultiChoiceValue(item: MultiChoiceSetting): BooleanArray {
val value = item.selectedValues;
val valuesId = item.valuesId;
if (valuesId > 0) {
val valuesArray = context.resources.getIntArray(valuesId);
val res = BooleanArray(valuesArray.size){false}
for (index in valuesArray.indices) {
if (value.contains(valuesArray[index])) {
res[index] = true;
}
}
return res;
}
return BooleanArray(1){false};
}
} }

View file

@ -14,6 +14,7 @@ import android.os.Build
import android.text.TextUtils import android.text.TextUtils
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.serialization.builtins.IntArraySerializer
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
@ -24,12 +25,14 @@ import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.FloatSetting import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.StringSetting import org.citra.citra_emu.features.settings.model.StringSetting
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
import org.citra.citra_emu.features.settings.model.view.HeaderSetting import org.citra.citra_emu.features.settings.model.view.HeaderSetting
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.RunnableSetting import org.citra.citra_emu.features.settings.model.view.RunnableSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
@ -1106,6 +1109,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.UPRIGHT_SCREEN.defaultValue BooleanSetting.UPRIGHT_SCREEN.defaultValue
) )
) )
add(
MultiChoiceSetting(
IntListSetting.LAYOUTS_TO_CYCLE,
R.string.layouts_to_cycle,
R.string.layouts_to_cycle_description,
R.array.landscapeLayouts,
R.array.landscapeLayoutValues,
IntListSetting.LAYOUTS_TO_CYCLE.key,
IntListSetting.LAYOUTS_TO_CYCLE.defaultValue
)
)
add( add(
SingleChoiceSetting( SingleChoiceSetting(
IntSetting.PORTRAIT_SCREEN_LAYOUT, IntSetting.PORTRAIT_SCREEN_LAYOUT,

View file

@ -0,0 +1,80 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.ui.viewholder
import android.view.View
import org.citra.citra_emu.databinding.ListItemSettingBinding
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
private lateinit var setting: SettingsItem
override fun bind(item: SettingsItem) {
setting = item
binding.textSettingName.setText(item.nameId)
if (item.descriptionId != 0) {
binding.textSettingDescription.visibility = View.VISIBLE
binding.textSettingDescription.setText(item.descriptionId)
} else {
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.VISIBLE
binding.textSettingValue.text = getTextSetting()
if (setting.isActive) {
binding.textSettingName.alpha = 1f
binding.textSettingDescription.alpha = 1f
binding.textSettingValue.alpha = 1f
} else {
binding.textSettingName.alpha = 0.5f
binding.textSettingDescription.alpha = 0.5f
binding.textSettingValue.alpha = 0.5f
}
}
private fun getTextSetting(): String {
when (val item = setting) {
is MultiChoiceSetting -> {
val resMgr = binding.textSettingDescription.context.resources
val values = resMgr.getIntArray(item.valuesId)
var resList:List<String> = emptyList();
values.forEachIndexed { i: Int, value: Int ->
if ((setting as MultiChoiceSetting).selectedValues.contains(value)) {
resList = resList + resMgr.getStringArray(item.choicesId)[i];
}
}
return resList.joinToString();
}
else -> return ""
}
}
override fun onClick(clicked: View) {
if (!setting.isEditable || !setting.isEnabled) {
adapter.onClickDisabledSetting(!setting.isEditable)
return
}
if (setting is MultiChoiceSetting) {
adapter.onMultiChoiceClick(
(setting as MultiChoiceSetting),
bindingAdapterPosition
)
}
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting(!setting.isEditable)
}
return false
}
}

View file

@ -12,6 +12,7 @@ import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.FloatSetting import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
import org.citra.citra_emu.features.settings.model.SettingSection import org.citra.citra_emu.features.settings.model.SettingSection
@ -255,6 +256,11 @@ object SettingsFile {
return stringSetting return stringSetting
} }
val intListSetting = IntListSetting.from(key)
if (intListSetting != null) {
intListSetting.list = value.split(", ").map { it.toInt() }
}
return null return null
} }

View file

@ -341,6 +341,8 @@
<string name="layout_screen_orientation_landscape_reverse">Reverse Landscape</string> <string name="layout_screen_orientation_landscape_reverse">Reverse Landscape</string>
<string name="layout_screen_orientation_portrait">Portrait</string> <string name="layout_screen_orientation_portrait">Portrait</string>
<string name="layout_screen_orientation_portrait_reverse">Reverse Portrait</string> <string name="layout_screen_orientation_portrait_reverse">Reverse Portrait</string>
<string name="layouts_to_cycle">Layouts to Cycle</string>
<string name="layouts_to_cycle_description">Which layouts are cycled by the Cycle Layout hotkey</string>
<string name="aspect_ratio_default">Default</string> <string name="aspect_ratio_default">Default</string>
<string name="aspect_ratio_16_9">16:9</string> <string name="aspect_ratio_16_9">16:9</string>
<string name="aspect_ratio_4_3">4:3</string> <string name="aspect_ratio_4_3">4:3</string>

View file

@ -49,6 +49,9 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
configuration/configure_layout.cpp configuration/configure_layout.cpp
configuration/configure_layout.h configuration/configure_layout.h
configuration/configure_layout.ui configuration/configure_layout.ui
configuration/configure_layout_cycle.cpp
configuration/configure_layout_cycle.h
configuration/configure_layout_cycle.ui
configuration/configure_dialog.cpp configuration/configure_dialog.cpp
configuration/configure_dialog.h configuration/configure_dialog.h
configuration/configure_general.cpp configuration/configure_general.cpp

View file

@ -2694,25 +2694,22 @@ void GMainWindow::AdjustSpeedLimit(bool increase) {
void GMainWindow::ToggleScreenLayout() { void GMainWindow::ToggleScreenLayout() {
const Settings::LayoutOption new_layout = []() { const Settings::LayoutOption new_layout = []() {
switch (Settings::values.layout_option.GetValue()) { const Settings::LayoutOption current_layout = Settings::values.layout_option.GetValue();
case Settings::LayoutOption::Default: std::vector<Settings::LayoutOption> layouts_to_cycle =
return Settings::LayoutOption::SingleScreen; Settings::values.layouts_to_cycle.GetValue();
case Settings::LayoutOption::SingleScreen: const auto current_pos =
return Settings::LayoutOption::LargeScreen; distance(layouts_to_cycle.begin(),
case Settings::LayoutOption::LargeScreen: std::find(layouts_to_cycle.begin(), layouts_to_cycle.end(), current_layout));
return Settings::LayoutOption::HybridScreen; // if the layouts_to_cycle setting has somehow been
case Settings::LayoutOption::HybridScreen: // cleared out, add just default back in
return Settings::LayoutOption::SideScreen; if (layouts_to_cycle.size() == 0) {
case Settings::LayoutOption::SideScreen: layouts_to_cycle.push_back(Settings::LayoutOption::Default);
return Settings::LayoutOption::SeparateWindows; }
case Settings::LayoutOption::SeparateWindows: if (current_pos >= layouts_to_cycle.size() - 1) {
return Settings::LayoutOption::CustomLayout; // either this layout wasn't found or it was last so move to the beginning
case Settings::LayoutOption::CustomLayout: return layouts_to_cycle[0];
return Settings::LayoutOption::Default; } else {
default: return layouts_to_cycle[current_pos + 1];
LOG_ERROR(Frontend, "Unknown layout option {}",
Settings::values.layout_option.GetValue());
return Settings::LayoutOption::Default;
} }
}(); }();

View file

@ -6,6 +6,8 @@
#include <array> #include <array>
#include <QKeySequence> #include <QKeySequence>
#include <QSettings> #include <QSettings>
#include <QVariant>
#include <QVector>
#include "citra_qt/configuration/config.h" #include "citra_qt/configuration/config.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/settings.h" #include "common/settings.h"
@ -132,6 +134,35 @@ void QtConfig::ReadBasicSetting(Settings::Setting<std::string>& setting) {
setting.SetValue(qt_config->value(name, default_value).toString().toStdString()); setting.SetValue(qt_config->value(name, default_value).toString().toStdString());
} }
} }
// definition for vectors of enums
template <typename Type, bool ranged>
void QtConfig::ReadBasicSetting(Settings::Setting<std::vector<Type>, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const std::vector<Type> default_value = setting.GetDefault();
QStringList stringList = qt_config->value(name).toStringList();
if (qt_config->value(name + QStringLiteral("/default"), false).toBool() ||
stringList.size() < 1) {
setting.SetValue(default_value);
} else {
if (stringList.size() < 1) {
setting.SetValue(default_value);
} else {
std::vector<Type> newValue;
for (const QString& str : stringList) {
if constexpr (std::is_enum_v<Type>) {
using TypeU = std::underlying_type_t<Type>;
newValue.push_back(static_cast<Type>(str.toInt()));
} else if constexpr (std::is_integral_v<Type>) {
newValue.push_back(str.toInt());
} else {
newValue.push_back(str.toStdString());
}
}
setting.SetValue(newValue);
}
}
}
template <typename Type, bool ranged> template <typename Type, bool ranged>
void QtConfig::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) { void QtConfig::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) {
@ -158,27 +189,7 @@ void QtConfig::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& sett
const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
setting.SetGlobal(use_global); setting.SetGlobal(use_global);
if (global || !use_global) { if (global || !use_global) {
QVariant default_value{}; ReadBasicSetting(setting);
if constexpr (std::is_enum_v<Type>) {
using TypeU = std::underlying_type_t<Type>;
default_value = QVariant::fromValue<TypeU>(static_cast<TypeU>(setting.GetDefault()));
setting.SetValue(static_cast<Type>(ReadSetting(name, default_value).value<TypeU>()));
} else {
default_value = QVariant::fromValue<Type>(setting.GetDefault());
setting.SetValue(ReadSetting(name, default_value).value<Type>());
}
}
}
template <>
void QtConfig::ReadGlobalSetting(Settings::SwitchableSetting<std::string>& setting) {
QString name = QString::fromStdString(setting.GetLabel());
const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
setting.SetGlobal(use_global);
if (global || !use_global) {
const QString default_value = QString::fromStdString(setting.GetDefault());
setting.SetValue(
ReadSetting(name, QVariant::fromValue(default_value)).toString().toStdString());
} }
} }
@ -187,16 +198,41 @@ template <>
void QtConfig::WriteBasicSetting(const Settings::Setting<std::string>& setting) { void QtConfig::WriteBasicSetting(const Settings::Setting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel()); const QString name = QString::fromStdString(setting.GetLabel());
const std::string& value = setting.GetValue(); const std::string& value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); if (global)
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
qt_config->setValue(name, QString::fromStdString(value)); qt_config->setValue(name, QString::fromStdString(value));
} }
template <typename Type, bool ranged>
void QtConfig::WriteBasicSetting(const Settings::Setting<std::vector<Type>, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const std::vector<Type>& value = setting.GetValue();
if (global)
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
QStringList stringList;
if constexpr (std::is_enum_v<Type>) {
// For enums, convert to underlying integer type strings
using TypeU = std::underlying_type_t<Type>;
for (const Type& item : value) {
stringList.append(QString::number(static_cast<TypeU>(item)));
}
} else {
// For non-enum types (assuming numeric)
for (const Type& item : value) {
stringList.append(QString::number(item));
}
}
qt_config->setValue(name, stringList);
}
// Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable // Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable
template <> template <>
void QtConfig::WriteBasicSetting(const Settings::Setting<u16>& setting) { void QtConfig::WriteBasicSetting(const Settings::Setting<u16>& setting) {
const QString name = QString::fromStdString(setting.GetLabel()); const QString name = QString::fromStdString(setting.GetLabel());
const u16& value = setting.GetValue(); const u16& value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); if (global)
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
qt_config->setValue(name, static_cast<u32>(value)); qt_config->setValue(name, static_cast<u32>(value));
} }
@ -204,7 +240,8 @@ template <typename Type, bool ranged>
void QtConfig::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) { void QtConfig::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel()); const QString name = QString::fromStdString(setting.GetLabel());
const Type value = setting.GetValue(); const Type value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); if (global)
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
if constexpr (std::is_enum_v<Type>) { if constexpr (std::is_enum_v<Type>) {
qt_config->setValue(name, static_cast<std::underlying_type_t<Type>>(value)); qt_config->setValue(name, static_cast<std::underlying_type_t<Type>>(value));
} else { } else {
@ -215,44 +252,11 @@ void QtConfig::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting)
template <typename Type, bool ranged> template <typename Type, bool ranged>
void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) { void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel()); const QString name = QString::fromStdString(setting.GetLabel());
const Type& value = setting.GetValue(global);
if (!global) { if (!global) {
qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal()); qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
} }
if (global || !setting.UsingGlobal()) { if (global || !setting.UsingGlobal()) {
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); WriteBasicSetting(setting);
if constexpr (std::is_enum_v<Type>) {
qt_config->setValue(name, static_cast<std::underlying_type_t<Type>>(value));
} else {
qt_config->setValue(name, QVariant::fromValue(value));
}
}
}
template <>
void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const std::string& value = setting.GetValue(global);
if (!global) {
qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
}
if (global || !setting.UsingGlobal()) {
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
qt_config->setValue(name, QString::fromStdString(value));
}
}
// Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable
template <>
void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting<u16, true>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const u16& value = setting.GetValue(global);
if (!global) {
qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
}
if (global || !setting.UsingGlobal()) {
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
qt_config->setValue(name, static_cast<u32>(value));
} }
} }
@ -526,7 +530,7 @@ void QtConfig::ReadLayoutValues() {
ReadGlobalSetting(Settings::values.large_screen_proportion); ReadGlobalSetting(Settings::values.large_screen_proportion);
ReadGlobalSetting(Settings::values.screen_gap); ReadGlobalSetting(Settings::values.screen_gap);
ReadGlobalSetting(Settings::values.small_screen_position); ReadGlobalSetting(Settings::values.small_screen_position);
ReadGlobalSetting(Settings::values.layouts_to_cycle);
if (global) { if (global) {
ReadBasicSetting(Settings::values.mono_render_option); ReadBasicSetting(Settings::values.mono_render_option);
ReadBasicSetting(Settings::values.custom_top_x); ReadBasicSetting(Settings::values.custom_top_x);
@ -1091,6 +1095,7 @@ void QtConfig::SaveLayoutValues() {
WriteGlobalSetting(Settings::values.large_screen_proportion); WriteGlobalSetting(Settings::values.large_screen_proportion);
WriteGlobalSetting(Settings::values.screen_gap); WriteGlobalSetting(Settings::values.screen_gap);
WriteGlobalSetting(Settings::values.small_screen_position); WriteGlobalSetting(Settings::values.small_screen_position);
WriteGlobalSetting(Settings::values.layouts_to_cycle);
if (global) { if (global) {
WriteBasicSetting(Settings::values.mono_render_option); WriteBasicSetting(Settings::values.mono_render_option);
WriteBasicSetting(Settings::values.custom_top_x); WriteBasicSetting(Settings::values.custom_top_x);
@ -1418,7 +1423,8 @@ void QtConfig::WriteSetting(const QString& name, const QVariant& value) {
void QtConfig::WriteSetting(const QString& name, const QVariant& value, void QtConfig::WriteSetting(const QString& name, const QVariant& value,
const QVariant& default_value) { const QVariant& default_value) {
qt_config->setValue(name + QStringLiteral("/default"), value == default_value); if (global)
qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
qt_config->setValue(name, value); qt_config->setValue(name, value);
} }

View file

@ -120,6 +120,10 @@ private:
template <typename Type, bool ranged> template <typename Type, bool ranged>
void ReadBasicSetting(Settings::Setting<Type, ranged>& setting); void ReadBasicSetting(Settings::Setting<Type, ranged>& setting);
// Add overload for vectors
template <typename Type, bool ranged>
void ReadBasicSetting(Settings::Setting<std::vector<Type>, ranged>& setting);
/** Sets a value from the setting in the qt_config using the setting's label and default value. /** Sets a value from the setting in the qt_config using the setting's label and default value.
* *
* @param The setting * @param The setting
@ -127,6 +131,9 @@ private:
template <typename Type, bool ranged> template <typename Type, bool ranged>
void WriteBasicSetting(const Settings::Setting<Type, ranged>& setting); void WriteBasicSetting(const Settings::Setting<Type, ranged>& setting);
template <typename Type, bool ranged>
void WriteBasicSetting(const Settings::Setting<std::vector<Type>, ranged>& setting);
ConfigType type; ConfigType type;
std::unique_ptr<QSettings> qt_config; std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc; std::string qt_config_loc;

View file

@ -6,6 +6,7 @@
#include <QtGlobal> #include <QtGlobal>
#include "citra_qt/configuration/configuration_shared.h" #include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_layout.h" #include "citra_qt/configuration/configure_layout.h"
#include "citra_qt/configuration/configure_layout_cycle.h"
#include "common/settings.h" #include "common/settings.h"
#include "ui_configure_layout.h" #include "ui_configure_layout.h"
#ifdef ENABLE_OPENGL #ifdef ENABLE_OPENGL
@ -111,6 +112,13 @@ ConfigureLayout::ConfigureLayout(QWidget* parent)
ui->bg_button->setIcon(color_icon); ui->bg_button->setIcon(color_icon);
ui->bg_button->setEnabled(true); ui->bg_button->setEnabled(true);
}); });
connect(ui->customize_layouts_to_cycle, &QPushButton::clicked, this, [this] {
ui->customize_layouts_to_cycle->setEnabled(false);
QDialog* layout_cycle_dialog = new ConfigureLayoutCycle(this);
layout_cycle_dialog->exec();
ui->customize_layouts_to_cycle->setEnabled(true);
});
} }
ConfigureLayout::~ConfigureLayout() = default; ConfigureLayout::~ConfigureLayout() = default;

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>705</width> <width>659</width>
<height>656</height> <height>662</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
@ -51,8 +51,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>688</width> <width>646</width>
<height>799</height> <height>824</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -130,18 +130,39 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="toggle_swap_screen"> <layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="text"> <item>
<string>Swap screens</string> <layout class="QVBoxLayout" name="verticalLayout_2">
</property> <item>
</widget> <widget class="QCheckBox" name="toggle_upright_screen">
</item> <property name="text">
<item> <string>Rotate screens upright</string>
<widget class="QCheckBox" name="toggle_upright_screen"> </property>
<property name="text"> </widget>
<string>Rotate screens upright</string> </item>
</property> <item>
</widget> <widget class="QCheckBox" name="toggle_swap_screen">
<property name="text">
<string>Swap screens</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="customize_layouts_to_cycle">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Customize layout cycling</string>
</property>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="QWidget" name="widget" native="true"> <widget class="QWidget" name="widget" native="true">
@ -356,7 +377,7 @@
<item row="0" column="1"> <item row="0" column="1">
<widget class="QSpinBox" name="custom_top_x"> <widget class="QSpinBox" name="custom_top_x">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -376,7 +397,7 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="custom_top_y"> <widget class="QSpinBox" name="custom_top_y">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -396,7 +417,7 @@
<item row="2" column="1"> <item row="2" column="1">
<widget class="QSpinBox" name="custom_top_width"> <widget class="QSpinBox" name="custom_top_width">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -416,7 +437,7 @@
<item row="3" column="1"> <item row="3" column="1">
<widget class="QSpinBox" name="custom_top_height"> <widget class="QSpinBox" name="custom_top_height">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -440,6 +461,12 @@
<property name="title"> <property name="title">
<string>Bottom Screen</string> <string>Bottom Screen</string>
</property> </property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_1"> <layout class="QGridLayout" name="gridLayout_1">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="lb_bottom_x"> <widget class="QLabel" name="lb_bottom_x">
@ -451,7 +478,7 @@
<item row="0" column="1"> <item row="0" column="1">
<widget class="QSpinBox" name="custom_bottom_x"> <widget class="QSpinBox" name="custom_bottom_x">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -471,7 +498,7 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="custom_bottom_y"> <widget class="QSpinBox" name="custom_bottom_y">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -491,7 +518,7 @@
<item row="2" column="1"> <item row="2" column="1">
<widget class="QSpinBox" name="custom_bottom_width"> <widget class="QSpinBox" name="custom_bottom_width">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -511,7 +538,7 @@
<item row="3" column="1"> <item row="3" column="1">
<widget class="QSpinBox" name="custom_bottom_height"> <widget class="QSpinBox" name="custom_bottom_height">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -538,7 +565,7 @@
<item> <item>
<widget class="QSpinBox" name="custom_second_layer_opacity"> <widget class="QSpinBox" name="custom_second_layer_opacity">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::PlusMinus</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="minimum"> <property name="minimum">
<number>10</number> <number>10</number>
@ -583,7 +610,7 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="screen_top_leftright_padding"> <widget class="QSpinBox" name="screen_top_leftright_padding">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -610,7 +637,7 @@
<item row="2" column="1"> <item row="2" column="1">
<widget class="QSpinBox" name="screen_top_topbottom_padding"> <widget class="QSpinBox" name="screen_top_topbottom_padding">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -659,7 +686,7 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="screen_bottom_leftright_padding"> <widget class="QSpinBox" name="screen_bottom_leftright_padding">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -672,7 +699,7 @@
<item row="2" column="1"> <item row="2" column="1">
<widget class="QSpinBox" name="screen_bottom_topbottom_padding"> <widget class="QSpinBox" name="screen_bottom_topbottom_padding">
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum> <enum>QAbstractSpinBox::UpDownArrows</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string>px</string> <string>px</string>
@ -717,9 +744,6 @@
</item> </item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>20</width> <width>20</width>

View file

@ -0,0 +1,81 @@
#include <QCloseEvent>
#include <QDialog>
#include "citra_qt/configuration/configure_layout_cycle.h"
#include "ui_configure_layout_cycle.h"
ConfigureLayoutCycle::ConfigureLayoutCycle(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureLayoutCycle>()) {
ui->setupUi(this);
SetConfiguration();
ConnectEvents();
}
// You MUST define the destructor in the .cpp file
ConfigureLayoutCycle::~ConfigureLayoutCycle() = default;
void ConfigureLayoutCycle::ConnectEvents() {
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
&ConfigureLayoutCycle::ApplyConfiguration);
connect(ui->globalCheck, &QCheckBox::stateChanged, this, &ConfigureLayoutCycle::UpdateGlobal);
}
void ConfigureLayoutCycle::SetConfiguration() {
if (Settings::IsConfiguringGlobal()) {
ui->globalCheck->setChecked(true);
ui->globalCheck->setVisible(false);
} else {
ui->globalCheck->setChecked(Settings::values.layouts_to_cycle.UsingGlobal());
ui->checkGroup->setDisabled(Settings::values.layouts_to_cycle.UsingGlobal());
}
for (auto option : Settings::values.layouts_to_cycle.GetValue()) {
switch (option) {
case Settings::LayoutOption::Default:
ui->defaultCheck->setChecked(true);
break;
case Settings::LayoutOption::SingleScreen:
ui->singleCheck->setChecked(true);
break;
case Settings::LayoutOption::LargeScreen:
ui->largeCheck->setChecked(true);
break;
case Settings::LayoutOption::SideScreen:
ui->sidebysideCheck->setChecked(true);
break;
case Settings::LayoutOption::SeparateWindows:
ui->separateCheck->setChecked(true);
break;
case Settings::LayoutOption::HybridScreen:
ui->hybridCheck->setChecked(true);
break;
case Settings::LayoutOption::CustomLayout:
ui->customCheck->setChecked(true);
break;
}
}
}
void ConfigureLayoutCycle::ApplyConfiguration() {
std::vector<Settings::LayoutOption> newSetting{};
if (ui->defaultCheck->isChecked())
newSetting.push_back(Settings::LayoutOption::Default);
if (ui->singleCheck->isChecked())
newSetting.push_back(Settings::LayoutOption::SingleScreen);
if (ui->sidebysideCheck->isChecked())
newSetting.push_back(Settings::LayoutOption::SideScreen);
if (ui->largeCheck->isChecked())
newSetting.push_back(Settings::LayoutOption::LargeScreen);
if (ui->separateCheck->isChecked())
newSetting.push_back(Settings::LayoutOption::SeparateWindows);
if (ui->hybridCheck->isChecked())
newSetting.push_back(Settings::LayoutOption::HybridScreen);
if (ui->customCheck->isChecked())
newSetting.push_back(Settings::LayoutOption::CustomLayout);
Settings::values.layouts_to_cycle = newSetting;
accept();
}
void ConfigureLayoutCycle::UpdateGlobal() {
Settings::values.layouts_to_cycle.SetGlobal(ui->globalCheck->isChecked());
ui->checkGroup->setDisabled(ui->globalCheck->isChecked());
ui->checkGroup->repaint(); // Force visual update
}

View file

@ -0,0 +1,32 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
#include "common/settings.h"
namespace Ui {
class ConfigureLayoutCycle;
}
class ConfigureLayoutCycle : public QDialog {
Q_OBJECT
public:
explicit ConfigureLayoutCycle(QWidget* parent = nullptr);
~ConfigureLayoutCycle() override;
public slots:
void ApplyConfiguration();
private slots:
private:
void SetConfiguration();
void ConnectEvents();
void UpdateGlobal();
std::unique_ptr<Ui::ConfigureLayoutCycle> ui;
};

View file

@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureLayoutCycle</class>
<widget class="QDialog" name="ConfigureLayoutCycle">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>395</width>
<height>334</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Configure Layout Cycling</string>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>381</width>
<height>323</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,1,0,0,1">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>Screen Layout Cycling Customization</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="instructions">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Select which screen layout options should be cycled with the &quot;Toggle Screen Layout&quot; hotkey</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="globalCheck">
<property name="text">
<string>Use Global Value</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="checkGroup">
<property name="enabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="vertLayout">
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="defaultCheck">
<property name="text">
<string>Default</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="singleCheck">
<property name="text">
<string>Single Screen</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="largeCheck">
<property name="text">
<string>Large Screen</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="sidebysideCheck">
<property name="text">
<string>Side by Side</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="separateCheck">
<property name="text">
<string>Separate Windows</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="hybridCheck">
<property name="text">
<string>Hybrid</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="customCheck">
<property name="text">
<string>Custom</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureLayoutCycle</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureLayoutCycle</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -120,6 +120,7 @@ void LogSettings() {
log_setting("Layout_ScreenGap", values.screen_gap.GetValue()); log_setting("Layout_ScreenGap", values.screen_gap.GetValue());
log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue()); log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue());
log_setting("Layout_SmallScreenPosition", values.small_screen_position.GetValue()); log_setting("Layout_SmallScreenPosition", values.small_screen_position.GetValue());
// log_setting("Layout_LayoutsToCycle",values.layouts_to_cycle.GetValue());
log_setting("Utility_DumpTextures", values.dump_textures.GetValue()); log_setting("Utility_DumpTextures", values.dump_textures.GetValue());
log_setting("Utility_CustomTextures", values.custom_textures.GetValue()); log_setting("Utility_CustomTextures", values.custom_textures.GetValue());
log_setting("Utility_PreloadTextures", values.preload_textures.GetValue()); log_setting("Utility_PreloadTextures", values.preload_textures.GetValue());
@ -209,6 +210,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.layout_option.SetGlobal(true); values.layout_option.SetGlobal(true);
values.portrait_layout_option.SetGlobal(true); values.portrait_layout_option.SetGlobal(true);
values.secondary_display_layout.SetGlobal(true); values.secondary_display_layout.SetGlobal(true);
values.layouts_to_cycle.SetGlobal(true);
values.swap_screen.SetGlobal(true); values.swap_screen.SetGlobal(true);
values.upright_screen.SetGlobal(true); values.upright_screen.SetGlobal(true);
values.large_screen_proportion.SetGlobal(true); values.large_screen_proportion.SetGlobal(true);

View file

@ -522,6 +522,14 @@ struct Values {
SwitchableSetting<bool> upright_screen{false, "upright_screen"}; SwitchableSetting<bool> upright_screen{false, "upright_screen"};
SwitchableSetting<SecondaryDisplayLayout> secondary_display_layout{SecondaryDisplayLayout::None, SwitchableSetting<SecondaryDisplayLayout> secondary_display_layout{SecondaryDisplayLayout::None,
"secondary_display_layout"}; "secondary_display_layout"};
SwitchableSetting<std::vector<LayoutOption>> layouts_to_cycle{
{LayoutOption::Default, LayoutOption::SingleScreen, LayoutOption::LargeScreen,
LayoutOption::SideScreen,
#ifndef ANDROID
LayoutOption::SeparateWindows,
#endif
LayoutOption::HybridScreen, LayoutOption::CustomLayout},
"layouts_to_cycle"};
SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f, SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f,
"large_screen_proportion"}; "large_screen_proportion"};
SwitchableSetting<int> screen_gap{0, "screen_gap"}; SwitchableSetting<int> screen_gap{0, "screen_gap"};