diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiBooleanSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiBooleanSetting.kt new file mode 100644 index 000000000..a4cf9ca3d --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiBooleanSetting.kt @@ -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 +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiFloatSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiFloatSetting.kt new file mode 100644 index 000000000..f1a21ed8d --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiFloatSetting.kt @@ -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 +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiIntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiIntSetting.kt new file mode 100644 index 000000000..eda30030e --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiIntSetting.kt @@ -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 +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiShortSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiShortSetting.kt new file mode 100644 index 000000000..6ff003b40 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiShortSetting.kt @@ -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 +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiStringSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiStringSetting.kt new file mode 100644 index 000000000..9d1735290 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractMultiStringSetting.kt @@ -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 +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt index 92f74d7c8..16b31492d 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt @@ -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? = null, override var isEnabled: Boolean = true ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_MULTI_CHOICE - val selectedValue: Int + val selectedValues: List 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): 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): AbstractMultiShortSetting { + val shortSetting = setting as AbstractMultiShortSetting + shortSetting.shorts = selection return shortSetting } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt index 68aa2226c..a1c0dd6ce 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt @@ -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 } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringMultiChoiceSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringMultiChoiceSetting.kt new file mode 100644 index 000000000..9b420ad07 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringMultiChoiceSetting.kt @@ -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, + val values: Array?, + val key: String? = null, + private val defaultValue: List? = 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 + 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() + 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): AbstractMultiStringSetting { + val stringSetting = setting as AbstractMultiStringSetting + stringSetting.strings = selection + return stringSetting + } + + fun setSelectedValues(selection: List): AbstractMultiShortSetting { + val shortSetting = setting as AbstractMultiShortSetting + shortSetting.shorts = selection + return shortSetting + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt index bc55bd5d6..2ac2162c0 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt @@ -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(), DialogInterface.OnClickListener { +) : RecyclerView.Adapter(), DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener { private var settings: ArrayList? = 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() + 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() + } + } + } + } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt new file mode 100644 index 000000000..76fe809fc --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt @@ -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 + } +} \ No newline at end of file