Added Multi Choice Impl. Need to Refactor OnClick for MultiChoice and debug Impl when adding combo button.

This commit is contained in:
adas20 2025-07-22 14:19:35 -04:00 committed by OpenSauce04
parent 77081d002a
commit bfcafccb8e
10 changed files with 363 additions and 15 deletions

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 AbstractMultiBooleanSetting : AbstractSetting {
var booleans: List<Boolean>
}

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 AbstractMultiFloatSetting : AbstractSetting {
var floats: List<Float>
}

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 AbstractMultiIntSetting : AbstractSetting {
var ints: List<Int>
}

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 AbstractMultiShortSetting : AbstractSetting {
var shorts: List<Short>
}

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 AbstractMultiStringSetting : AbstractSetting {
var strings: List<String>
}

View File

@ -4,9 +4,9 @@
package org.citra.citra_emu.features.settings.model.view
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractMultiIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
import org.citra.citra_emu.features.settings.model.AbstractMultiShortSetting
class MultiChoiceSetting(
setting: AbstractSetting?,
@ -15,26 +15,26 @@ class MultiChoiceSetting(
val choicesId: Int,
val valuesId: Int,
val key: String? = null,
val defaultValues: Int? = null,
val defaultValues: List<Int>? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_MULTI_CHOICE
val selectedValue: Int
val selectedValues: List<Int>
get() {
if (setting == null) {
return defaultValues!!
}
try {
val setting = setting as AbstractIntSetting
return setting.int
val setting = setting as AbstractMultiIntSetting
return setting.ints
} catch (_: ClassCastException) {
}
try {
val setting = setting as AbstractShortSetting
return setting.short.toInt()
val setting = setting as AbstractMultiShortSetting
return setting.shorts.map { it.toInt() }
} catch (_: ClassCastException) {
}
@ -48,15 +48,15 @@ class MultiChoiceSetting(
* @param selection New value of the int.
* @return the existing setting with the new value applied.
*/
fun setSelectedValue(selection: Int): AbstractIntSetting {
val intSetting = setting as AbstractIntSetting
intSetting.int = selection
fun setSelectedValues(selection: List<Int>): AbstractMultiIntSetting {
val intSetting = setting as AbstractMultiIntSetting
intSetting.ints = selection
return intSetting
}
fun setSelectedValue(selection: Short): AbstractShortSetting {
val shortSetting = setting as AbstractShortSetting
shortSetting.short = selection
fun setSelectedValues(selection: List<Short>): AbstractMultiShortSetting {
val shortSetting = setting as AbstractMultiShortSetting
shortSetting.shorts = selection
return shortSetting
}
}

View File

@ -48,5 +48,6 @@ abstract class SettingsItem(
const val TYPE_STRING_INPUT = 9
const val TYPE_FLOAT_INPUT = 10
const val TYPE_MULTI_CHOICE = 11
const val TYPE_STRING_MULTI_CHOICE = 12
}
}

View File

@ -0,0 +1,92 @@
// 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.AbstractMultiShortSetting
import org.citra.citra_emu.features.settings.model.AbstractMultiStringSetting
class StringMultiChoiceSetting(
setting: AbstractSetting?,
titleId: Int,
descriptionId: Int,
val choices: Array<String>,
val values: Array<String>?,
val key: String? = null,
private val defaultValue: List<String>? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_STRING_MULTI_CHOICE
fun getValueAt(index: Int): String? {
if (values == null) return null
return if (index >= 0 && index < values.size) {
values[index]
} else {
""
}
}
val selectedValues: List<String>
get() {
if (setting == null) {
return defaultValue!!
}
try {
val setting = setting as AbstractMultiStringSetting
return setting.strings
} catch (_: ClassCastException) {
}
try {
val setting = setting as AbstractMultiShortSetting
return setting.shorts.map { it.toString() }
} catch (_: ClassCastException) {
}
return defaultValue!!
}
val selectValueIndices: BooleanArray
get() {
val noneList = values?.let {
ArrayList(BooleanArray(it.size) { false }.toList())
} ?: ArrayList()
val chosenindices = mutableListOf<Boolean>()
val selectedValues = selectedValues
for (i in values!!.indices) {
if (values[i] in selectedValues) {
chosenindices.add(true)
} else {
chosenindices.add(false)
}
}
if (chosenindices == null) {
return noneList.toBooleanArray()
} else {
return chosenindices.toBooleanArray()
}
}
/**
* Write a value to the backing int. If that int was previously null,
* initializes a new one and returns it, so it can be added to the Hashmap.
*
* @param selection New value of the int.
* @return the existing setting with the new value applied.
*/
fun setSelectedValues(selection: List<String>): AbstractMultiStringSetting {
val stringSetting = setting as AbstractMultiStringSetting
stringSetting.strings = selection
return stringSetting
}
fun setSelectedValues(selection: List<Short>): AbstractMultiShortSetting {
val shortSetting = setting as AbstractMultiShortSetting
shortSetting.shorts = selection
return shortSetting
}
}

View File

@ -45,16 +45,19 @@ 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.view.DateTimeSetting
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.SettingsItem
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
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.StringMultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting
import org.citra.citra_emu.features.settings.model.view.SubmenuSetting
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.HeaderViewHolder
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.SettingViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.SingleChoiceViewHolder
@ -72,7 +75,7 @@ import kotlin.math.roundToInt
class SettingsAdapter(
private val fragmentView: SettingsFragmentView,
public val context: Context
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
private var settings: ArrayList<SettingsItem>? = null
private var clickedItem: SettingsItem? = null
private var clickedPosition: Int
@ -128,6 +131,10 @@ class SettingsAdapter(
StringInputViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
SettingsItem.TYPE_MULTI_CHOICE, SettingsItem.TYPE_STRING_MULTI_CHOICE -> {
MultiChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
else -> {
// TODO: Create an error view since we can't return null now
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
@ -632,4 +639,113 @@ class SettingsAdapter(
}
return -1
}
private fun getSelectionForMultiChoiceValues(item: MultiChoiceSetting): BooleanArray {
val checked_values = mutableListOf<Boolean>()
val values = item.selectedValues
val valuesId = item.valuesId
if (valuesId > 0) {
val valuesArray = context.resources.getIntArray(valuesId)
for (index in valuesArray.indices) {
val current = valuesArray[index]
if (current in values) {
checked_values.add(true)
} else {
checked_values.add(false)
}
}
}
if (checked_values == null) {
return booleanArrayOf(false)
} else {
return checked_values.toBooleanArray()
}
}
private fun onMultiChoiceClick(item: MultiChoiceSetting) {
clickedItem = item
val values = getSelectionForMultiChoiceValues(item)
dialog = MaterialAlertDialogBuilder(context)
.setTitle(item.nameId)
.setMultiChoiceItems(item.choicesId, values, this)
.show()
}
fun onMultiChoiceClick(item: MultiChoiceSetting, position: Int) {
clickedPosition = position
onMultiChoiceClick(item)
}
private fun onStringMultiChoiceClick(item: StringMultiChoiceSetting) {
clickedItem = item
dialog = context?.let {
MaterialAlertDialogBuilder(it)
.setTitle(item.nameId)
.setMultiChoiceItems(item.choices, item.selectValueIndices, this)
.show()
}
}
fun onStringMultiChoiceClick(item: StringMultiChoiceSetting, position: Int) {
clickedPosition = position
onStringMultiChoiceClick(item)
}
//TODO: REFACTOR TO BE MULTICHOICE
override fun onClick(dialog: DialogInterface?, which: Int, is_checked: Boolean) {
when (clickedItem) {
is SingleChoiceSetting -> {
val scSetting = clickedItem as? SingleChoiceSetting
scSetting?.let {
val setting = when (it.setting) {
is AbstractIntSetting -> {
val value = getValueForSingleChoiceSelection(it, which)
if (it.selectedValue != value) {
fragmentView?.onSettingChanged()
}
it.setSelectedValue(value)
}
is AbstractShortSetting -> {
val value = getValueForSingleChoiceSelection(it, which).toShort()
if (it.selectedValue.toShort() != value) {
fragmentView?.onSettingChanged()
}
it.setSelectedValue(value)
}
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
}
fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
closeDialog()
}
}
is StringSingleChoiceSetting -> {
val scSetting = clickedItem as? StringSingleChoiceSetting
scSetting?.let {
val setting = when (it.setting) {
is AbstractStringSetting -> {
val value = it.getValueAt(which)
if (it.selectedValue != value) fragmentView?.onSettingChanged()
it.setSelectedValue(value ?: "")
}
is AbstractShortSetting -> {
if (it.selectValueIndex != which) fragmentView?.onSettingChanged()
it.setSelectedValue(it.getValueAt(which)?.toShort() ?: 1)
}
else -> throw IllegalStateException("Unrecognized type used for StringSingleChoiceSetting!")
}
fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
closeDialog()
}
}
}
}
}

View File

@ -0,0 +1,94 @@
// 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.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.StringMultiChoiceSetting
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)
values.forEachIndexed { i: Int, value: Int ->
if (value in (setting as MultiChoiceSetting).selectedValues) {
return resMgr.getStringArray(item.choicesId)[i]
}
}
return ""
}
is StringMultiChoiceSetting -> {
item.values?.forEachIndexed { i: Int, value: String ->
if (value in item.selectedValues) {
return item.choices[i]
}
}
return ""
}
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
)
} else if (setting is StringMultiChoiceSetting) {
adapter.onStringMultiChoiceClick(
(setting as StringMultiChoiceSetting),
bindingAdapterPosition
)
}
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting(!setting.isEditable)
}
return false
}
}