Filter on screen buttons by controller type

This commit is contained in:
Willi Ye 2021-04-30 21:38:13 +02:00 committed by ◱ Mark
parent 2ff06d5421
commit ce60101989
4 changed files with 97 additions and 80 deletions

View File

@ -218,7 +218,10 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
// Hide on screen controls when first controller is not set // Hide on screen controls when first controller is not set
binding.onScreenControllerView.apply { binding.onScreenControllerView.apply {
isGone = inputManager.controllers[0]!!.type == ControllerType.None || !settings.onScreenControl inputManager.controllers[0]!!.type.let {
controllerType = it
isGone = it == ControllerType.None || !settings.onScreenControl
}
setOnButtonStateChangedListener(::onButtonStateChanged) setOnButtonStateChangedListener(::onButtonStateChanged)
setOnStickStateChangedListener(::onStickStateChanged) setOnStickStateChangedListener(::onStickStateChanged)
recenterSticks = settings.onScreenControlRecenterSticks recenterSticks = settings.onScreenControlRecenterSticks
@ -414,14 +417,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
private fun onButtonStateChanged(buttonId : ButtonId, state : ButtonState) = setButtonState(0, buttonId.value(), state.state) private fun onButtonStateChanged(buttonId : ButtonId, state : ButtonState) = setButtonState(0, buttonId.value(), state.state)
private fun onStickStateChanged(buttonId : ButtonId, position : PointF) { private fun onStickStateChanged(stickId : StickId, position : PointF) {
val stickId = when (buttonId) {
ButtonId.LeftStick -> StickId.Left
ButtonId.RightStick -> StickId.Right
else -> error("Invalid button ID")
}
setAxisValue(0, stickId.xAxis.ordinal, (position.x * Short.MAX_VALUE).toInt()) setAxisValue(0, stickId.xAxis.ordinal, (position.x * Short.MAX_VALUE).toInt())
setAxisValue(0, stickId.yAxis.ordinal, (-position.y * Short.MAX_VALUE).toInt()) // Y is inverted, since drawing starts from top left setAxisValue(0, stickId.yAxis.ordinal, (-position.y * Short.MAX_VALUE).toInt()) // Y is inverted, since drawing starts from top left
} }

View File

@ -17,22 +17,35 @@ import android.view.View
import android.view.View.OnTouchListener import android.view.View.OnTouchListener
import emu.skyline.input.ButtonId import emu.skyline.input.ButtonId
import emu.skyline.input.ButtonState import emu.skyline.input.ButtonState
import emu.skyline.input.ControllerType
import emu.skyline.input.StickId
import emu.skyline.utils.add import emu.skyline.utils.add
import emu.skyline.utils.multiply import emu.skyline.utils.multiply
import emu.skyline.utils.normalize import emu.skyline.utils.normalize
import kotlin.math.roundToLong import kotlin.math.roundToLong
typealias OnButtonStateChangedListener = (buttonId : ButtonId, state : ButtonState) -> Unit typealias OnButtonStateChangedListener = (buttonId : ButtonId, state : ButtonState) -> Unit
typealias OnStickStateChangedListener = (buttonId : ButtonId, position : PointF) -> Unit typealias OnStickStateChangedListener = (stickId : StickId, position : PointF) -> Unit
/** /**
* Renders On-Screen Controls as a single view, handles touch inputs and button toggling * Renders On-Screen Controls as a single view, handles touch inputs and button toggling
*/ */
class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = 0, defStyleRes : Int = 0) : View(context, attrs, defStyleAttr, defStyleRes) { class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = 0, defStyleRes : Int = 0) : View(context, attrs, defStyleAttr, defStyleRes) {
companion object {
private val controllerTypeMappings = mapOf(*ControllerType.values().map {
it to (setOf(*it.buttons) to setOf(*it.sticks))
}.toTypedArray())
}
private val controls = Controls(this) private val controls = Controls(this)
private var onButtonStateChangedListener : OnButtonStateChangedListener? = null private var onButtonStateChangedListener : OnButtonStateChangedListener? = null
private var onStickStateChangedListener : OnStickStateChangedListener? = null private var onStickStateChangedListener : OnStickStateChangedListener? = null
private val joystickAnimators = mutableMapOf<JoystickButton, Animator?>() private val joystickAnimators = mutableMapOf<JoystickButton, Animator?>()
var controllerType : ControllerType? = null
set(value) {
field = value
invalidate()
}
var recenterSticks = false var recenterSticks = false
set(value) { set(value) {
field = value field = value
@ -42,11 +55,16 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
override fun onDraw(canvas : Canvas) { override fun onDraw(canvas : Canvas) {
super.onDraw(canvas) super.onDraw(canvas)
controls.allButtons.forEach { val allowedIds = controllerTypeMappings[controllerType]
if (it.config.enabled) { controls.allButtons.forEach { button ->
it.width = width if (button.config.enabled
it.height = height && allowedIds?.let { (buttonIds, stickIds) ->
it.render(canvas) if (button is JoystickButton) stickIds.contains(button.stickId) else buttonIds.contains(button.buttonId)
} != false
) {
button.width = width
button.height = height
button.render(canvas)
} }
} }
} }
@ -128,14 +146,14 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
val vector = direction.multiply(value) val vector = direction.multiply(value)
val newPosition = position.add(vector) val newPosition = position.add(vector)
joystick.onFingerMoved(newPosition.x, newPosition.y, false) joystick.onFingerMoved(newPosition.x, newPosition.y, false)
onStickStateChangedListener?.invoke(joystick.buttonId, vector.multiply(1f / radius)) onStickStateChangedListener?.invoke(joystick.stickId, vector.multiply(1f / radius))
invalidate() invalidate()
} }
addListener(object : AnimatorListenerAdapter() { addListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation : Animator?) { override fun onAnimationCancel(animation : Animator?) {
super.onAnimationCancel(animation) super.onAnimationCancel(animation)
onAnimationEnd(animation) onAnimationEnd(animation)
onStickStateChangedListener?.invoke(joystick.buttonId, PointF(0f, 0f)) onStickStateChangedListener?.invoke(joystick.stickId, PointF(0f, 0f))
} }
override fun onAnimationEnd(animation : Animator?) { override fun onAnimationEnd(animation : Animator?) {
@ -163,7 +181,7 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
if (joystick.shortDoubleTapped) if (joystick.shortDoubleTapped)
onButtonStateChangedListener?.invoke(joystick.buttonId, ButtonState.Pressed) onButtonStateChangedListener?.invoke(joystick.buttonId, ButtonState.Pressed)
if (recenterSticks) if (recenterSticks)
onStickStateChangedListener?.invoke(joystick.buttonId, joystick.outerToInnerRelative()) onStickStateChangedListener?.invoke(joystick.stickId, joystick.outerToInnerRelative())
performClick() performClick()
handled = true handled = true
} }
@ -173,7 +191,7 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
for (i in 0 until event.pointerCount) { for (i in 0 until event.pointerCount) {
if (event.getPointerId(i) == joystick.touchPointerId) { if (event.getPointerId(i) == joystick.touchPointerId) {
val centerToPoint = joystick.onFingerMoved(event.getX(i), event.getY(i)) val centerToPoint = joystick.onFingerMoved(event.getX(i), event.getY(i))
onStickStateChangedListener?.invoke(joystick.buttonId, centerToPoint) onStickStateChangedListener?.invoke(joystick.stickId, centerToPoint)
handled = true handled = true
} }
} }

View File

@ -12,25 +12,27 @@ import androidx.core.graphics.minus
import emu.skyline.R import emu.skyline.R
import emu.skyline.input.ButtonId import emu.skyline.input.ButtonId
import emu.skyline.input.ButtonId.* import emu.skyline.input.ButtonId.*
import emu.skyline.input.StickId
import emu.skyline.input.StickId.*
import emu.skyline.utils.add import emu.skyline.utils.add
import emu.skyline.utils.multiply import emu.skyline.utils.multiply
import kotlin.math.roundToInt import kotlin.math.roundToInt
open class CircularButton( open class CircularButton(
onScreenControllerView : OnScreenControllerView, onScreenControllerView : OnScreenControllerView,
buttonId : ButtonId, buttonId : ButtonId,
defaultRelativeX : Float, defaultRelativeX : Float,
defaultRelativeY : Float, defaultRelativeY : Float,
defaultRelativeRadiusToX : Float, defaultRelativeRadiusToX : Float,
drawableId : Int = R.drawable.ic_button drawableId : Int = R.drawable.ic_button
) : OnScreenButton( ) : OnScreenButton(
onScreenControllerView, onScreenControllerView,
buttonId, buttonId,
defaultRelativeX, defaultRelativeX,
defaultRelativeY, defaultRelativeY,
defaultRelativeRadiusToX * 2f, defaultRelativeRadiusToX * 2f,
defaultRelativeRadiusToX * CONFIGURED_ASPECT_RATIO * 2f, defaultRelativeRadiusToX * CONFIGURED_ASPECT_RATIO * 2f,
drawableId drawableId
) { ) {
val radius get() = itemWidth / 2f val radius get() = itemWidth / 2f
@ -49,18 +51,18 @@ open class CircularButton(
} }
class JoystickButton( class JoystickButton(
onScreenControllerView : OnScreenControllerView, onScreenControllerView : OnScreenControllerView,
buttonId : ButtonId, val stickId : StickId,
defaultRelativeX : Float, defaultRelativeX : Float,
defaultRelativeY : Float, defaultRelativeY : Float,
defaultRelativeRadiusToX : Float defaultRelativeRadiusToX : Float
) : CircularButton( ) : CircularButton(
onScreenControllerView, onScreenControllerView,
buttonId, stickId.button,
defaultRelativeX, defaultRelativeX,
defaultRelativeY, defaultRelativeY,
defaultRelativeRadiusToX, defaultRelativeRadiusToX,
R.drawable.ic_button R.drawable.ic_button
) { ) {
private val innerButton = CircularButton(onScreenControllerView, buttonId, config.relativeX, config.relativeY, defaultRelativeRadiusToX * 0.75f, R.drawable.ic_stick) private val innerButton = CircularButton(onScreenControllerView, buttonId, config.relativeX, config.relativeY, defaultRelativeRadiusToX * 0.75f, R.drawable.ic_stick)
@ -155,21 +157,21 @@ class JoystickButton(
} }
open class RectangularButton( open class RectangularButton(
onScreenControllerView : OnScreenControllerView, onScreenControllerView : OnScreenControllerView,
buttonId : ButtonId, buttonId : ButtonId,
defaultRelativeX : Float, defaultRelativeX : Float,
defaultRelativeY : Float, defaultRelativeY : Float,
defaultRelativeWidth : Float, defaultRelativeWidth : Float,
defaultRelativeHeight : Float, defaultRelativeHeight : Float,
drawableId : Int = R.drawable.ic_rectangular_button drawableId : Int = R.drawable.ic_rectangular_button
) : OnScreenButton( ) : OnScreenButton(
onScreenControllerView, onScreenControllerView,
buttonId, buttonId,
defaultRelativeX, defaultRelativeX,
defaultRelativeY, defaultRelativeY,
defaultRelativeWidth, defaultRelativeWidth,
defaultRelativeHeight, defaultRelativeHeight,
drawableId drawableId
) { ) {
override fun isTouched(x : Float, y : Float) = currentBounds.contains(x.roundToInt(), y.roundToInt()) override fun isTouched(x : Float, y : Float) = currentBounds.contains(x.roundToInt(), y.roundToInt())
@ -183,26 +185,26 @@ open class RectangularButton(
} }
class TriggerButton( class TriggerButton(
onScreenControllerView : OnScreenControllerView, onScreenControllerView : OnScreenControllerView,
buttonId : ButtonId, buttonId : ButtonId,
defaultRelativeX : Float, defaultRelativeX : Float,
defaultRelativeY : Float, defaultRelativeY : Float,
defaultRelativeWidth : Float, defaultRelativeWidth : Float,
defaultRelativeHeight : Float defaultRelativeHeight : Float
) : RectangularButton( ) : RectangularButton(
onScreenControllerView, onScreenControllerView,
buttonId, buttonId,
defaultRelativeX, defaultRelativeX,
defaultRelativeY, defaultRelativeY,
defaultRelativeWidth, defaultRelativeWidth,
defaultRelativeHeight, defaultRelativeHeight,
when (buttonId) { when (buttonId) {
ZL -> R.drawable.ic_trigger_button_left ZL -> R.drawable.ic_trigger_button_left
ZR -> R.drawable.ic_trigger_button_right ZR -> R.drawable.ic_trigger_button_right
else -> error("Unsupported trigger button") else -> error("Unsupported trigger button")
} }
) )
class Controls(onScreenControllerView : OnScreenControllerView) { class Controls(onScreenControllerView : OnScreenControllerView) {
@ -229,14 +231,14 @@ class Controls(onScreenControllerView : OnScreenControllerView) {
val buttonPairs = circularButtonPairs + triggerButtonPairs val buttonPairs = circularButtonPairs + triggerButtonPairs
val circularButtons = circularButtonPairs.flatten() + listOf( val circularButtons = circularButtonPairs.flatten() + listOf(
CircularButton(onScreenControllerView, Plus, 0.57f, 0.75f, 0.025f), CircularButton(onScreenControllerView, Plus, 0.57f, 0.75f, 0.025f),
CircularButton(onScreenControllerView, Minus, 0.43f, 0.75f, 0.025f), CircularButton(onScreenControllerView, Minus, 0.43f, 0.75f, 0.025f),
CircularButton(onScreenControllerView, Menu, 0.5f, 0.75f, 0.025f) CircularButton(onScreenControllerView, Menu, 0.5f, 0.75f, 0.025f)
) )
val joysticks = listOf( val joysticks = listOf(
JoystickButton(onScreenControllerView, LeftStick, 0.1f, 0.8f, 0.05f), JoystickButton(onScreenControllerView, Left, 0.1f, 0.8f, 0.05f),
JoystickButton(onScreenControllerView, RightStick, 0.75f, 0.6f, 0.05f) JoystickButton(onScreenControllerView, Right, 0.75f, 0.6f, 0.05f)
) )
val rectangularButtons = listOf(buttonL, buttonR) val rectangularButtons = listOf(buttonL, buttonR)

View File

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity"> tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
@ -20,7 +21,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
app:layout_scrollFlags="enterAlwaysCollapsed"> app:layout_scrollFlags="scroll">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"