mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-12-16 12:08:49 +00:00
Merge a438094d44 into b0fe4d190d
This commit is contained in:
commit
a6b0e9f0af
@ -79,6 +79,36 @@ class InputBindingSetting(
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is an up or down dpad button
|
||||
*/
|
||||
|
||||
fun isVerticalButton(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
Settings.KEY_BUTTON_DOWN,
|
||||
Settings.KEY_BUTTON_UP -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun isVerticalAxis(): Boolean {
|
||||
return abstractSetting.key ==
|
||||
Settings.KEY_DPAD_AXIS_VERTICAL
|
||||
}
|
||||
|
||||
fun isHorizontalAxis(): Boolean {
|
||||
return abstractSetting.key ==
|
||||
Settings.KEY_DPAD_AXIS_HORIZONTAL
|
||||
}
|
||||
|
||||
fun isHorizontalButton(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
Settings.KEY_BUTTON_LEFT,
|
||||
Settings.KEY_BUTTON_RIGHT -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this key is for the 3DS L/R or ZL/ZR buttons. Note, these are not real
|
||||
* triggers on the 3DS, but we support them as such on a physical gamepad.
|
||||
@ -332,5 +362,22 @@ class InputBindingSetting(
|
||||
event.keyCode
|
||||
}
|
||||
}
|
||||
|
||||
fun getInputObject(key: String, preferences: SharedPreferences): AbstractStringSetting {
|
||||
return object : AbstractStringSetting {
|
||||
override var string: String
|
||||
get() = preferences.getString(key, "")!!
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putString(key, value)
|
||||
.apply()
|
||||
}
|
||||
override val key = key
|
||||
override val section = Settings.SECTION_CONTROLS
|
||||
override val isRuntimeEditable = true
|
||||
override val valueAsString = preferences.getString(key, "")!!
|
||||
override val defaultValue = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,226 @@
|
||||
package org.citra.citra_emu.features.settings.ui
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.DialogControllerQuickConfigBinding
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.utils.InputBindingBase
|
||||
|
||||
class ControllerQuickConfigDialog(
|
||||
private var context: Context,
|
||||
buttons: ArrayList<List<String>>,
|
||||
titles: ArrayList<List<Int>>,
|
||||
private var preferences: SharedPreferences,
|
||||
adapter: SettingsAdapter
|
||||
) {
|
||||
private var isWaiting = false;
|
||||
private var index = 0
|
||||
val inflater = LayoutInflater.from(context)
|
||||
private lateinit var quickConfigBinding: DialogControllerQuickConfigBinding
|
||||
var dialog: AlertDialog? = null
|
||||
private var boundVerticalDpadAxis = false
|
||||
private var boundHorizontalDpadAxis = false
|
||||
|
||||
var allButtons = arrayListOf<String>()
|
||||
var allTitles = arrayListOf<Int>()
|
||||
|
||||
private val inputHandler = object : InputBindingBase() {
|
||||
private val AXIS_WAIT_TIME = 400L
|
||||
private val BUTTON_WAIT_TIME = 100L
|
||||
|
||||
private val handler = android.os.Handler(android.os.Looper.getMainLooper())
|
||||
|
||||
override fun onAxisCaptured() {
|
||||
boundHorizontalDpadAxis =
|
||||
boundHorizontalDpadAxis || setting != null && setting!!.isVerticalAxis()
|
||||
boundVerticalDpadAxis =
|
||||
boundVerticalDpadAxis || setting != null && setting!!.isHorizontalAxis()
|
||||
onInputCaptured(AXIS_WAIT_TIME)
|
||||
}
|
||||
|
||||
override fun onButtonCaptured() {
|
||||
onInputCaptured(BUTTON_WAIT_TIME)
|
||||
}
|
||||
|
||||
private fun onInputCaptured(waitTime: Long) {
|
||||
if (isWaiting) {
|
||||
return
|
||||
}
|
||||
quickConfigBinding.root.cancelPendingInputEvents()
|
||||
index++
|
||||
setting?.let { settingsList.add(it) }
|
||||
isWaiting = true
|
||||
quickConfigBinding.currentMappingTitle.text = context.getString(R.string.controller_quick_config_wait)
|
||||
|
||||
// the changed item after each setting is one more than the index, since the Quick Configure button is position 1
|
||||
adapter.notifyItemChanged(index + 1)
|
||||
|
||||
// clear listeners during the waiting period, they will get reset once ready for input
|
||||
dialog?.setOnKeyListener(null)
|
||||
quickConfigBinding.root.setOnGenericMotionListener(null)
|
||||
|
||||
// Wait before preparing the next input
|
||||
handler.postDelayed({
|
||||
isWaiting = false
|
||||
prepareUIforIndex()
|
||||
}, waitTime)
|
||||
}
|
||||
|
||||
override fun getCurrentSetting() = setting
|
||||
}
|
||||
|
||||
init {
|
||||
buttons.forEach { group ->
|
||||
group.forEach { button ->
|
||||
allButtons.add(button)
|
||||
}
|
||||
}
|
||||
titles.forEach { group ->
|
||||
group.forEach { title ->
|
||||
allTitles.add(title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun show() {
|
||||
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
|
||||
quickConfigBinding = DialogControllerQuickConfigBinding.inflate(inflater)
|
||||
builder
|
||||
.setView(quickConfigBinding.root)
|
||||
.setTitle(context.getString(R.string.controller_quick_config))
|
||||
.setPositiveButton(context.getString(R.string.next)) { _, _ -> }
|
||||
.setNegativeButton(context.getString(R.string.close)) { dialog, which ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
dialog = builder.create()
|
||||
dialog?.show()
|
||||
|
||||
quickConfigBinding.root.requestFocus()
|
||||
quickConfigBinding.root.setOnFocusChangeListener { v, hasFocus ->
|
||||
if (!hasFocus) v.requestFocus()
|
||||
}
|
||||
|
||||
// Prepare the first element
|
||||
prepareUIforIndex()
|
||||
|
||||
val nextButton = dialog?.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
nextButton?.setOnClickListener {
|
||||
if (setting != null) setting!!.removeOldMapping()
|
||||
index++
|
||||
prepareUIforIndex()
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareUIforIndex() {
|
||||
if (index >= allButtons.size) {
|
||||
dialog?.dismiss()
|
||||
return
|
||||
}
|
||||
setting = InputBindingSetting(
|
||||
InputBindingSetting.getInputObject(allButtons[index], preferences),
|
||||
allTitles[index]
|
||||
)
|
||||
// skip the dpad buttons if the corresponding axis is mapped
|
||||
while (setting != null && (
|
||||
setting!!.isVerticalButton() && boundVerticalDpadAxis ||
|
||||
setting!!.isHorizontalButton() && boundHorizontalDpadAxis)
|
||||
) {
|
||||
index++
|
||||
if (index >= allButtons.size) {
|
||||
dialog?.dismiss()
|
||||
return
|
||||
}
|
||||
setting = InputBindingSetting(
|
||||
InputBindingSetting.getInputObject(
|
||||
allButtons[index],
|
||||
preferences
|
||||
), allTitles[index]
|
||||
)
|
||||
|
||||
}
|
||||
// show the previous key, if this isn't the first key
|
||||
if (index > 0) {
|
||||
quickConfigBinding.lastMappingIcon.visibility = View.VISIBLE
|
||||
quickConfigBinding.lastMappingDescription.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
// change the button layout for the last button
|
||||
if (index == allButtons.size - 1) {
|
||||
dialog?.getButton(AlertDialog.BUTTON_POSITIVE)?.text =
|
||||
context.getString(R.string.finish)
|
||||
dialog?.getButton(AlertDialog.BUTTON_NEGATIVE)?.visibility = View.GONE
|
||||
}
|
||||
|
||||
// set all the icons and text
|
||||
var lastTitle = setting?.value ?: ""
|
||||
if (lastTitle.isBlank()) {
|
||||
lastTitle = context.getString(R.string.unassigned)
|
||||
}
|
||||
quickConfigBinding.lastMappingDescription.text = lastTitle
|
||||
quickConfigBinding.lastMappingIcon.setImageDrawable(quickConfigBinding.currentMappingIcon.drawable)
|
||||
|
||||
quickConfigBinding.currentMappingTitle.text = calculateTitle()
|
||||
quickConfigBinding.currentMappingDescription.text = setting?.value
|
||||
quickConfigBinding.currentMappingIcon.setImageDrawable(getIcon())
|
||||
|
||||
// reset all the handlers
|
||||
if (setting!!.isButtonMappingSupported()) {
|
||||
dialog?.setOnKeyListener { _, _, event -> inputHandler.onKeyEvent(event) }
|
||||
}
|
||||
if (setting!!.isAxisMappingSupported()) {
|
||||
quickConfigBinding.root.setOnGenericMotionListener { _, event ->
|
||||
inputHandler.onMotionEvent(
|
||||
event
|
||||
)
|
||||
}
|
||||
}
|
||||
inputHandler.reset()
|
||||
|
||||
}
|
||||
|
||||
private fun calculateTitle(): String {
|
||||
val inputTypeId = when {
|
||||
setting!!.isCirclePad() -> R.string.controller_circlepad
|
||||
setting!!.isCStick() -> R.string.controller_c
|
||||
setting!!.isDPad() -> R.string.controller_dpad
|
||||
setting!!.isTrigger() -> R.string.controller_trigger
|
||||
else -> R.string.button
|
||||
}
|
||||
|
||||
val nameId = setting?.nameId?.let { context.getString(it) }
|
||||
|
||||
return String.format(
|
||||
context.getString(R.string.input_dialog_title),
|
||||
context.getString(inputTypeId),
|
||||
nameId
|
||||
)
|
||||
}
|
||||
|
||||
private fun getIcon(): Drawable? {
|
||||
val id = when {
|
||||
setting!!.isCirclePad() -> R.drawable.stick_main
|
||||
setting!!.isCStick() -> R.drawable.stick_c
|
||||
setting!!.isDPad() -> R.drawable.dpad
|
||||
else -> {
|
||||
val resourceTitle = context.resources.getResourceEntryName(setting!!.nameId)
|
||||
if (resourceTitle.startsWith("direction")) {
|
||||
R.drawable.dpad
|
||||
} else {
|
||||
context.resources.getIdentifier(resourceTitle, "drawable", context.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ContextCompat.getDrawable(context, id)
|
||||
}
|
||||
|
||||
private var setting: InputBindingSetting? = null
|
||||
|
||||
private var settingsList = arrayListOf<InputBindingSetting>()
|
||||
}
|
||||
@ -43,6 +43,7 @@ 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.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
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.SettingsItem
|
||||
@ -64,6 +65,7 @@ import org.citra.citra_emu.features.settings.ui.viewholder.SubmenuViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.SwitchSettingViewHolder
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import java.lang.NumberFormatException
|
||||
import java.text.SimpleDateFormat
|
||||
@ -610,6 +612,30 @@ class SettingsAdapter(
|
||||
.show()
|
||||
}
|
||||
|
||||
fun onClickControllerQuickConfig() {
|
||||
|
||||
val buttons = arrayListOf(
|
||||
Settings.buttonKeys,
|
||||
Settings.circlePadKeys,
|
||||
Settings.cStickKeys,
|
||||
Settings.dPadAxisKeys,
|
||||
Settings.dPadButtonKeys,
|
||||
Settings.triggerKeys
|
||||
)
|
||||
|
||||
val titles = arrayListOf(
|
||||
Settings.buttonTitles,
|
||||
Settings.axisTitles,
|
||||
Settings.axisTitles,
|
||||
Settings.axisTitles,
|
||||
Settings.dPadTitles,
|
||||
Settings.triggerTitles
|
||||
)
|
||||
|
||||
ControllerQuickConfigDialog(context, buttons, titles, PermissionsHandler.preferences, this).show()
|
||||
|
||||
}
|
||||
|
||||
fun closeDialog() {
|
||||
if (dialog != null) {
|
||||
if (clickedPosition != -1) {
|
||||
|
||||
@ -761,44 +761,54 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
private fun addControlsSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_controls))
|
||||
sl.apply {
|
||||
add(
|
||||
RunnableSetting(
|
||||
R.string.controller_quick_config,
|
||||
R.string.controller_quick_config_description,
|
||||
true,
|
||||
0,
|
||||
{ settingsAdapter.onClickControllerQuickConfig() }
|
||||
)
|
||||
)
|
||||
|
||||
add(HeaderSetting(R.string.generic_buttons))
|
||||
Settings.buttonKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
val button = InputBindingSetting.getInputObject(key, preferences)
|
||||
add(InputBindingSetting(button, Settings.buttonTitles[i]))
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.controller_circlepad))
|
||||
Settings.circlePadKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
val button = InputBindingSetting.getInputObject(key, preferences)
|
||||
add(InputBindingSetting(button, Settings.axisTitles[i]))
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.controller_c))
|
||||
Settings.cStickKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
val button = InputBindingSetting.getInputObject(key, preferences)
|
||||
add(InputBindingSetting(button, Settings.axisTitles[i]))
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.controller_dpad_axis,R.string.controller_dpad_axis_description))
|
||||
Settings.dPadAxisKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
val button = InputBindingSetting.getInputObject(key, preferences)
|
||||
add(InputBindingSetting(button, Settings.axisTitles[i]))
|
||||
}
|
||||
add(HeaderSetting(R.string.controller_dpad_button,R.string.controller_dpad_button_description))
|
||||
Settings.dPadButtonKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
val button = InputBindingSetting.getInputObject(key, preferences)
|
||||
add(InputBindingSetting(button, Settings.dPadTitles[i]))
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.controller_triggers))
|
||||
Settings.triggerKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
val button = InputBindingSetting.getInputObject(key, preferences)
|
||||
add(InputBindingSetting(button, Settings.triggerTitles[i]))
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.controller_hotkeys))
|
||||
Settings.hotKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
val button = InputBindingSetting.getInputObject(key, preferences)
|
||||
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
|
||||
}
|
||||
add(HeaderSetting(R.string.miscellaneous))
|
||||
@ -814,23 +824,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInputObject(key: String): AbstractStringSetting {
|
||||
return object : AbstractStringSetting {
|
||||
override var string: String
|
||||
get() = preferences.getString(key, "")!!
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putString(key, value)
|
||||
.apply()
|
||||
}
|
||||
override val key = key
|
||||
override val section = Settings.SECTION_CONTROLS
|
||||
override val isRuntimeEditable = true
|
||||
override val valueAsString = preferences.getString(key, "")!!
|
||||
override val defaultValue = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
|
||||
sl.apply {
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
package org.citra.citra_emu.features.settings.utils
|
||||
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import kotlin.math.abs
|
||||
|
||||
abstract class InputBindingBase {
|
||||
private val previousValues = ArrayList<Float>()
|
||||
private var prevDeviceId = 0
|
||||
private var waitingForEvent = true
|
||||
|
||||
abstract fun onButtonCaptured()
|
||||
abstract fun onAxisCaptured()
|
||||
abstract fun getCurrentSetting(): InputBindingSetting?
|
||||
|
||||
fun reset() {
|
||||
previousValues.clear()
|
||||
prevDeviceId = 0
|
||||
waitingForEvent = true
|
||||
}
|
||||
|
||||
fun onMotionEvent(event: MotionEvent): Boolean {
|
||||
if ((event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) ||
|
||||
event.action != MotionEvent.ACTION_MOVE) return false
|
||||
|
||||
val input = event.device
|
||||
val motionRanges = input.motionRanges
|
||||
|
||||
if (input.id != prevDeviceId) {
|
||||
previousValues.clear()
|
||||
}
|
||||
prevDeviceId = input.id
|
||||
val firstEvent = previousValues.isEmpty()
|
||||
|
||||
var numMovedAxis = 0
|
||||
var axisMoveValue = 0.0f
|
||||
var lastMovedRange: InputDevice.MotionRange? = null
|
||||
var lastMovedDir = '?'
|
||||
|
||||
if (waitingForEvent) {
|
||||
for (i in motionRanges.indices) {
|
||||
val range = motionRanges[i]
|
||||
val axis = range.axis
|
||||
val origValue = event.getAxisValue(axis)
|
||||
if (firstEvent) {
|
||||
previousValues.add(origValue)
|
||||
} else {
|
||||
val previousValue = previousValues[i]
|
||||
|
||||
if (abs(origValue) > 0.5f && origValue != previousValue) {
|
||||
if (origValue != axisMoveValue) {
|
||||
axisMoveValue = origValue
|
||||
numMovedAxis++
|
||||
lastMovedRange = range
|
||||
lastMovedDir = if (origValue < 0.0f) '-' else '+'
|
||||
}
|
||||
} else if (abs(origValue) < 0.25f && abs(previousValue) > 0.75f) {
|
||||
numMovedAxis++
|
||||
lastMovedRange = range
|
||||
lastMovedDir = if (previousValue < 0.0f) '-' else '+'
|
||||
}
|
||||
}
|
||||
previousValues[i] = origValue
|
||||
}
|
||||
|
||||
if (numMovedAxis == 1) {
|
||||
waitingForEvent = false
|
||||
getCurrentSetting()?.onMotionInput(input, lastMovedRange!!, lastMovedDir)
|
||||
onAxisCaptured()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun onKeyEvent(event: KeyEvent): Boolean {
|
||||
return when (event.action) {
|
||||
KeyEvent.ACTION_UP -> {
|
||||
getCurrentSetting()?.onKeyInput(event)
|
||||
onButtonCaptured()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,10 +6,7 @@ package org.citra.citra_emu.fragments
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
@ -17,10 +14,21 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.DialogInputBinding
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import kotlin.math.abs
|
||||
import org.citra.citra_emu.features.settings.utils.InputBindingBase
|
||||
|
||||
class MotionBottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||
|
||||
private val inputHandler = object : InputBindingBase() {
|
||||
override fun onButtonCaptured() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun onAxisCaptured() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun getCurrentSetting() = setting
|
||||
}
|
||||
private var _binding: DialogInputBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
@ -28,10 +36,6 @@ class MotionBottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||
private var onCancel: (() -> Unit)? = null
|
||||
private var onDismiss: (() -> Unit)? = null
|
||||
|
||||
private val previousValues = ArrayList<Float>()
|
||||
private var prevDeviceId = 0
|
||||
private var waitingForEvent = true
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (setting == null) {
|
||||
@ -57,10 +61,10 @@ class MotionBottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||
view.requestFocus()
|
||||
view.setOnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() }
|
||||
if (setting!!.isButtonMappingSupported()) {
|
||||
dialog?.setOnKeyListener { _, _, event -> onKeyEvent(event) }
|
||||
dialog?.setOnKeyListener { _, _, event -> inputHandler.onKeyEvent(event) }
|
||||
}
|
||||
if (setting!!.isAxisMappingSupported()) {
|
||||
binding.root.setOnGenericMotionListener { _, event -> onMotionEvent(event) }
|
||||
binding.root.setOnGenericMotionListener { _, event -> inputHandler.onMotionEvent(event) }
|
||||
}
|
||||
|
||||
val inputTypeId = when {
|
||||
@ -108,85 +112,7 @@ class MotionBottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||
onDismiss?.invoke()
|
||||
}
|
||||
|
||||
private fun onKeyEvent(event: KeyEvent): Boolean {
|
||||
Log.debug("[MotionBottomSheetDialogFragment] Received key event: " + event.action)
|
||||
return when (event.action) {
|
||||
KeyEvent.ACTION_UP -> {
|
||||
setting?.onKeyInput(event)
|
||||
dismiss()
|
||||
// Even if we ignore the key, we still consume it. Thus return true regardless.
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun onMotionEvent(event: MotionEvent): Boolean {
|
||||
Log.debug("[MotionBottomSheetDialogFragment] Received motion event: " + event.action)
|
||||
if (event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) return false
|
||||
if (event.action != MotionEvent.ACTION_MOVE) return false
|
||||
|
||||
val input = event.device
|
||||
|
||||
val motionRanges = input.motionRanges
|
||||
|
||||
if (input.id != prevDeviceId) {
|
||||
previousValues.clear()
|
||||
}
|
||||
prevDeviceId = input.id
|
||||
val firstEvent = previousValues.isEmpty()
|
||||
|
||||
var numMovedAxis = 0
|
||||
var axisMoveValue = 0.0f
|
||||
var lastMovedRange: InputDevice.MotionRange? = null
|
||||
var lastMovedDir = '?'
|
||||
if (waitingForEvent) {
|
||||
for (i in motionRanges.indices) {
|
||||
val range = motionRanges[i]
|
||||
val axis = range.axis
|
||||
val origValue = event.getAxisValue(axis)
|
||||
if (firstEvent) {
|
||||
previousValues.add(origValue)
|
||||
} else {
|
||||
val previousValue = previousValues[i]
|
||||
|
||||
// Only handle the axes that are not neutral (more than 0.5)
|
||||
// but ignore any axis that has a constant value (e.g. always 1)
|
||||
if (abs(origValue) > 0.5f && origValue != previousValue) {
|
||||
// It is common to have multiple axes with the same physical input. For example,
|
||||
// shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
|
||||
// To handle this, we ignore an axis motion that's the exact same as a motion
|
||||
// we already saw. This way, we ignore axes with two names, but catch the case
|
||||
// where a joystick is moved in two directions.
|
||||
// ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
|
||||
if (origValue != axisMoveValue) {
|
||||
axisMoveValue = origValue
|
||||
numMovedAxis++
|
||||
lastMovedRange = range
|
||||
lastMovedDir = if (origValue < 0.0f) '-' else '+'
|
||||
}
|
||||
} else if (abs(origValue) < 0.25f && abs(previousValue) > 0.75f) {
|
||||
// Special case for d-pads (axis value jumps between 0 and 1 without any values
|
||||
// in between). Without this, the user would need to press the d-pad twice
|
||||
// due to the first press being caught by the "if (firstEvent)" case further up.
|
||||
numMovedAxis++
|
||||
lastMovedRange = range
|
||||
lastMovedDir = if (previousValue < 0.0f) '-' else '+'
|
||||
}
|
||||
}
|
||||
previousValues[i] = origValue
|
||||
}
|
||||
|
||||
// If only one axis moved, that's the winner.
|
||||
if (numMovedAxis == 1) {
|
||||
waitingForEvent = false
|
||||
setting?.onMotionInput(input, lastMovedRange!!, lastMovedDir)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "MotionBottomSheetDialogFragment"
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
<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:id="@+id/linearLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/lastMappingIcon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/button_a"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="@+id/currentMappingIcon"
|
||||
app:layout_constraintStart_toStartOf="@+id/currentMappingIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/lastMappingDescription"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:enabled="false"
|
||||
android:text="TextView"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/lastMappingIcon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/lastMappingIcon"
|
||||
app:layout_constraintTop_toTopOf="@+id/lastMappingIcon" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/currentMappingIcon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/lastMappingIcon"
|
||||
app:srcCompat="@drawable/button_b" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currentMappingTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:text="TextView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/currentMappingIcon"
|
||||
app:layout_constraintTop_toTopOf="@+id/currentMappingIcon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currentMappingDescription"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:enabled="false"
|
||||
android:text="TextView"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/currentMappingIcon"
|
||||
app:layout_constraintTop_toBottomOf="@+id/currentMappingTitle" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -125,6 +125,9 @@
|
||||
<string name="controller_dpad_button_description">Only map the D-pad to these if you\'re facing issues with the D-Pad (Axis) button mappings.</string>
|
||||
<string name="controller_axis_vertical">Up/Down Axis</string>
|
||||
<string name="controller_axis_horizontal">Left/Right Axis</string>
|
||||
<string name="controller_quick_config">Quick Configure</string>
|
||||
<string name="controller_quick_config_description">Quickly configure all input buttons (not including hotkeys)</string>
|
||||
<string name="controller_quick_config_wait">Please Wait</string>
|
||||
<string name="direction_up">Up</string>
|
||||
<string name="direction_down">Down</string>
|
||||
<string name="direction_left">Left</string>
|
||||
@ -385,6 +388,8 @@
|
||||
<string name="dont_show_again">Don\'t show again</string>
|
||||
<string name="visibility">Visibility</string>
|
||||
<string name="information">Information</string>
|
||||
<string name="finish">Finish</string>
|
||||
<string name="unassigned">Unassigned</string>
|
||||
|
||||
<!-- Add Directory Screen-->
|
||||
<string name="select_game_folder">Select Game Folder</string>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user