mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Refactored input overlay code
This commit is contained in:
@@ -5,7 +5,6 @@ import android.os.Bundle
|
||||
import android.text.InputFilter.LengthFilter
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.Keep
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@@ -20,11 +19,9 @@ import info.cemu.cemu.databinding.ActivityEmulationBinding
|
||||
import info.cemu.cemu.emulation.EmulationFragment.OnEmulationErrorCallback
|
||||
import info.cemu.cemu.input.InputManager
|
||||
import info.cemu.cemu.nativeinterface.NativeSwkbd.setCurrentInputText
|
||||
import java.util.Objects
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class EmulationActivity : AppCompatActivity() {
|
||||
private var hasEmulationError = false
|
||||
private val inputManager = InputManager()
|
||||
private var emulationTextInputDialog: AlertDialog? = null
|
||||
|
||||
@@ -95,10 +92,6 @@ class EmulationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun onEmulationError(emulationError: String?) {
|
||||
if (hasEmulationError) {
|
||||
return
|
||||
}
|
||||
hasEmulationError = true
|
||||
val builder = MaterialAlertDialogBuilder(this)
|
||||
builder.setTitle(R.string.error)
|
||||
.setMessage(emulationError)
|
||||
@@ -143,19 +136,13 @@ class EmulationActivity : AppCompatActivity() {
|
||||
val inputEditText =
|
||||
inputEditTextLayout.requireViewById<EmulationTextInputEditText>(R.id.emulation_input_text)
|
||||
inputEditText.updateText(initialText)
|
||||
val dialog = MaterialAlertDialogBuilder(
|
||||
emulationActivityInstance!!
|
||||
)
|
||||
val dialog = MaterialAlertDialogBuilder(emulationActivityInstance!!)
|
||||
.setView(inputEditTextLayout)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(
|
||||
R.string.done
|
||||
) { d: DialogInterface?, w: Int -> }.show()
|
||||
val doneButton = Objects.requireNonNull(
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
)
|
||||
.setPositiveButton(R.string.done) { _, _ -> }.show()
|
||||
val doneButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)!!
|
||||
doneButton.isEnabled = false
|
||||
doneButton.setOnClickListener { _: View? -> inputEditText.onFinishedEdit() }
|
||||
doneButton.setOnClickListener { _ -> inputEditText.onFinishedEdit() }
|
||||
inputEditText.setOnTextChangedListener {
|
||||
doneButton.isEnabled = it.isNotEmpty()
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ import androidx.fragment.app.Fragment
|
||||
import info.cemu.cemu.R
|
||||
import info.cemu.cemu.databinding.FragmentEmulationBinding
|
||||
import info.cemu.cemu.input.SensorManager
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsProvider
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsManager
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySurfaceView
|
||||
import info.cemu.cemu.inputoverlay.OverlaySettings
|
||||
import info.cemu.cemu.nativeinterface.NativeEmulation
|
||||
import info.cemu.cemu.nativeinterface.NativeEmulation.clearSurface
|
||||
import info.cemu.cemu.nativeinterface.NativeEmulation.initializeRenderer
|
||||
@@ -35,40 +36,43 @@ import info.cemu.cemu.nativeinterface.NativeInput.onTouchDown
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.onTouchMove
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.onTouchUp
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
PopupMenu.OnMenuItemClickListener {
|
||||
private class OnSurfaceTouchListener(val isTV: Boolean) : OnTouchListener {
|
||||
var currentPointerId: Int = -1
|
||||
|
||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
if (currentPointerId != -1 && pointerId != currentPointerId) {
|
||||
return false
|
||||
}
|
||||
val x = event.getX(pointerIndex).toInt()
|
||||
val y = event.getY(pointerIndex).toInt()
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
onTouchDown(x, y, isTV)
|
||||
return true
|
||||
}
|
||||
private class OnSurfaceTouchListener(val isTV: Boolean) : OnTouchListener {
|
||||
var currentPointerId: Int = -1
|
||||
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
|
||||
currentPointerId = -1
|
||||
onTouchUp(x, y, isTV)
|
||||
return true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
onTouchMove(x, y, isTV)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
if (currentPointerId != -1 && pointerId != currentPointerId) {
|
||||
return false
|
||||
}
|
||||
val x = event.getX(pointerIndex).toInt()
|
||||
val y = event.getY(pointerIndex).toInt()
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
onTouchDown(x, y, isTV)
|
||||
return true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
|
||||
currentPointerId = -1
|
||||
onTouchUp(x, y, isTV)
|
||||
return true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
onTouchMove(x, y, isTV)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
PopupMenu.OnMenuItemClickListener {
|
||||
|
||||
|
||||
private inner class SurfaceHolderCallback(val isMainCanvas: Boolean) : SurfaceHolder.Callback {
|
||||
var surfaceSet: Boolean = false
|
||||
@@ -107,8 +111,9 @@ class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
private var isGameRunning = false
|
||||
private var padCanvas: SurfaceView? = null
|
||||
private var toast: Toast? = null
|
||||
private var binding: FragmentEmulationBinding? = null
|
||||
private lateinit var binding: FragmentEmulationBinding
|
||||
private var isMotionEnabled = false
|
||||
private lateinit var overlaySettings: OverlaySettings
|
||||
private var settingsMenu: PopupMenu? = null
|
||||
private var inputOverlaySurfaceView: InputOverlaySurfaceView? = null
|
||||
private var sensorManager: SensorManager? = null
|
||||
@@ -121,45 +126,43 @@ class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val inputOverlaySettingsProvider = InputOverlaySettingsProvider(requireContext())
|
||||
if (sensorManager == null) {
|
||||
sensorManager = SensorManager(requireContext())
|
||||
}
|
||||
sensorManager!!.setIsLandscape(resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
val inputOverlaySettingsManager = InputOverlaySettingsManager(requireContext())
|
||||
overlaySettings = inputOverlaySettingsManager.overlaySettings
|
||||
sensorManager = SensorManager(requireContext())
|
||||
sensorManager?.setIsLandscape(resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
if (sensorManager != null) {
|
||||
sensorManager!!.setIsLandscape(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
}
|
||||
sensorManager?.setIsLandscape(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
sensorManager!!.pauseListening()
|
||||
sensorManager?.pauseListening()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (isMotionEnabled) {
|
||||
sensorManager!!.startListening()
|
||||
sensorManager?.startListening()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (sensorManager != null) {
|
||||
sensorManager!!.pauseListening()
|
||||
sensorManager?.pauseListening()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun createPadCanvas() {
|
||||
if (padCanvas != null) {
|
||||
return
|
||||
}
|
||||
padCanvas = SurfaceView(requireContext())
|
||||
binding!!.canvasesLayout.addView(
|
||||
binding.canvasesLayout.addView(
|
||||
padCanvas,
|
||||
LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f)
|
||||
)
|
||||
@@ -179,7 +182,7 @@ class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
if (padCanvas == null) {
|
||||
return
|
||||
}
|
||||
binding!!.canvasesLayout.removeView(padCanvas)
|
||||
binding.canvasesLayout.removeView(padCanvas)
|
||||
padCanvas = null
|
||||
}
|
||||
|
||||
@@ -200,9 +203,9 @@ class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
return true
|
||||
}
|
||||
if (itemId == R.id.edit_inputs) {
|
||||
binding!!.editInputsLayout.visibility = View.VISIBLE
|
||||
binding!!.finishEditInputsButton.visibility = View.VISIBLE
|
||||
binding!!.moveInputsButton.performClick()
|
||||
binding.editInputsLayout.visibility = View.VISIBLE
|
||||
binding.finishEditInputsButton.visibility = View.VISIBLE
|
||||
binding.moveInputsButton.performClick()
|
||||
return true
|
||||
}
|
||||
if (itemId == R.id.replace_tv_with_pad) {
|
||||
@@ -218,9 +221,9 @@ class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
if (itemId == R.id.enable_motion) {
|
||||
isMotionEnabled = !item.isChecked
|
||||
if (isMotionEnabled) {
|
||||
sensorManager!!.startListening()
|
||||
sensorManager?.startListening()
|
||||
} else {
|
||||
sensorManager!!.pauseListening()
|
||||
sensorManager?.pauseListening()
|
||||
}
|
||||
item.setChecked(isMotionEnabled)
|
||||
return true
|
||||
@@ -242,50 +245,51 @@ class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
return false
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
binding = FragmentEmulationBinding.inflate(inflater, container, false)
|
||||
inputOverlaySurfaceView = binding!!.inputOverlay
|
||||
inputOverlaySurfaceView = binding.inputOverlay
|
||||
|
||||
binding!!.moveInputsButton.setOnClickListener { v: View? ->
|
||||
binding.moveInputsButton.setOnClickListener { v: View? ->
|
||||
if (inputOverlaySurfaceView!!.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_POSITION) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
binding!!.resizeInputsButton.alpha = 0.5f
|
||||
binding!!.moveInputsButton.alpha = 1.0f
|
||||
binding.resizeInputsButton.alpha = 0.5f
|
||||
binding.moveInputsButton.alpha = 1.0f
|
||||
toastMessage(R.string.input_mode_edit_position)
|
||||
inputOverlaySurfaceView!!.setInputMode(InputOverlaySurfaceView.InputMode.EDIT_POSITION)
|
||||
}
|
||||
binding!!.resizeInputsButton.setOnClickListener { v: View? ->
|
||||
binding.resizeInputsButton.setOnClickListener { v: View? ->
|
||||
if (inputOverlaySurfaceView!!.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_SIZE) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
binding!!.moveInputsButton.alpha = 0.5f
|
||||
binding!!.resizeInputsButton.alpha = 1.0f
|
||||
binding.moveInputsButton.alpha = 0.5f
|
||||
binding.resizeInputsButton.alpha = 1.0f
|
||||
toastMessage(R.string.input_mode_edit_size)
|
||||
inputOverlaySurfaceView!!.setInputMode(InputOverlaySurfaceView.InputMode.EDIT_SIZE)
|
||||
}
|
||||
binding!!.finishEditInputsButton.setOnClickListener { v: View? ->
|
||||
binding.finishEditInputsButton.setOnClickListener { v: View? ->
|
||||
inputOverlaySurfaceView!!.setInputMode(InputOverlaySurfaceView.InputMode.DEFAULT)
|
||||
binding!!.finishEditInputsButton.visibility = View.GONE
|
||||
binding!!.editInputsLayout.visibility = View.GONE
|
||||
binding.finishEditInputsButton.visibility = View.GONE
|
||||
binding.editInputsLayout.visibility = View.GONE
|
||||
toastMessage(R.string.input_mode_default)
|
||||
}
|
||||
settingsMenu = PopupMenu(requireContext(), binding!!.emulationSettingsButton)
|
||||
settingsMenu = PopupMenu(requireContext(), binding.emulationSettingsButton)!!
|
||||
settingsMenu!!.menuInflater.inflate(R.menu.emulation, settingsMenu!!.menu)
|
||||
settingsMenu!!.setOnMenuItemClickListener(this@EmulationFragment)
|
||||
binding!!.emulationSettingsButton.setOnClickListener { v: View? -> settingsMenu!!.show() }
|
||||
binding.emulationSettingsButton.setOnClickListener { v: View? -> settingsMenu!!.show() }
|
||||
val menu = settingsMenu!!.menu
|
||||
// menu.findItem(R.id.show_input_overlay).setChecked(overlaySettings!!.isOverlayEnabled)
|
||||
// if (!overlaySettings!!.isOverlayEnabled) {
|
||||
// menu.findItem(R.id.reset_inputs).setEnabled(false)
|
||||
// menu.findItem(R.id.edit_inputs).setEnabled(false)
|
||||
// inputOverlaySurfaceView!!.visibility = View.GONE
|
||||
// }
|
||||
val mainCanvas = binding!!.mainCanvas
|
||||
menu.findItem(R.id.show_input_overlay).setChecked(overlaySettings!!.isOverlayEnabled)
|
||||
if (!overlaySettings!!.isOverlayEnabled) {
|
||||
menu.findItem(R.id.reset_inputs).setEnabled(false)
|
||||
menu.findItem(R.id.edit_inputs).setEnabled(false)
|
||||
inputOverlaySurfaceView!!.visibility = View.GONE
|
||||
}
|
||||
val mainCanvas = binding.mainCanvas
|
||||
try {
|
||||
val testSurfaceTexture = SurfaceTexture(0)
|
||||
val testSurface = Surface(testSurfaceTexture)
|
||||
@@ -299,7 +303,7 @@ class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
exception.message
|
||||
)
|
||||
)
|
||||
return binding!!.root
|
||||
return binding.root
|
||||
}
|
||||
|
||||
val mainCanvasHolder = mainCanvas.holder
|
||||
@@ -327,7 +331,7 @@ class EmulationFragment(private val launchPath: String) : Fragment(),
|
||||
}
|
||||
})
|
||||
mainCanvas.setOnTouchListener(OnSurfaceTouchListener(true))
|
||||
return binding!!.root
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun startGame() {
|
||||
|
||||
@@ -2,52 +2,23 @@ package info.cemu.cemu.inputoverlay
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.Rect
|
||||
import android.view.MotionEvent
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import info.cemu.cemu.drawable.getInvertedDrawable
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsProvider.InputOverlaySettings
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySurfaceView.OverlayButton
|
||||
import java.util.Objects
|
||||
|
||||
abstract class Button(
|
||||
resources: Resources,
|
||||
@DrawableRes buttonId: Int,
|
||||
buttonStateChangeListener: ButtonStateChangeListener,
|
||||
button: OverlayButton,
|
||||
settings: InputOverlaySettings
|
||||
) :
|
||||
Input(settings) {
|
||||
@JvmField
|
||||
protected var iconPressed: Drawable
|
||||
private val onButtonStateChange: (state: Boolean) -> Unit,
|
||||
boundingRect: Rect,
|
||||
) : Input(boundingRect) {
|
||||
|
||||
@JvmField
|
||||
protected var iconNotPressed: Drawable = ResourcesCompat.getDrawable(
|
||||
resources,
|
||||
buttonId,
|
||||
null
|
||||
)!!
|
||||
|
||||
@JvmField
|
||||
protected var icon: Drawable
|
||||
protected val inputDrawable = InputDrawable(resources, buttonId)
|
||||
|
||||
@JvmField
|
||||
protected var currentPointerId: Int = -1
|
||||
private val buttonStateChangeListener: ButtonStateChangeListener
|
||||
private val button: OverlayButton
|
||||
|
||||
init {
|
||||
iconPressed = iconNotPressed.getInvertedDrawable(resources)
|
||||
iconPressed.alpha = settings.alpha
|
||||
iconNotPressed.alpha = settings.alpha
|
||||
icon = iconNotPressed
|
||||
this.buttonStateChangeListener = buttonStateChangeListener
|
||||
this.button = button
|
||||
}
|
||||
private var currentPointerId: Int = -1
|
||||
|
||||
override fun onTouch(event: MotionEvent): Boolean {
|
||||
var stateUpdated = false
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
val pointerIndex = event.actionIndex
|
||||
@@ -56,7 +27,7 @@ abstract class Button(
|
||||
if (isInside(x, y)) {
|
||||
currentPointerId = event.getPointerId(pointerIndex)
|
||||
updateState(true)
|
||||
stateUpdated = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,27 +35,23 @@ abstract class Button(
|
||||
if (event.getPointerId(event.actionIndex) == currentPointerId) {
|
||||
currentPointerId = -1
|
||||
updateState(false)
|
||||
stateUpdated = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return stateUpdated
|
||||
return false
|
||||
}
|
||||
|
||||
override fun drawInput(canvas: Canvas) {
|
||||
icon.draw(canvas)
|
||||
inputDrawable.icon.draw(canvas)
|
||||
}
|
||||
|
||||
override fun resetInput() {
|
||||
updateState(false)
|
||||
}
|
||||
|
||||
protected fun updateState(state: Boolean) {
|
||||
icon = if (state) {
|
||||
iconPressed
|
||||
} else {
|
||||
iconNotPressed
|
||||
}
|
||||
buttonStateChangeListener.onButtonStateChange(button, state)
|
||||
private fun updateState(state: Boolean) {
|
||||
inputDrawable.setActiveState(state)
|
||||
onButtonStateChange(state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package info.cemu.cemu.inputoverlay
|
||||
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySurfaceView.OverlayButton
|
||||
|
||||
fun interface ButtonStateChangeListener {
|
||||
fun onButtonStateChange(button: OverlayButton?, state: Boolean)
|
||||
}
|
||||
@@ -2,12 +2,10 @@ package info.cemu.cemu.inputoverlay
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.Rect
|
||||
import android.view.MotionEvent
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import info.cemu.cemu.drawable.applyInvertedColorTransform
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsProvider.InputOverlaySettings
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.min
|
||||
|
||||
@@ -15,290 +13,110 @@ class DPadInput(
|
||||
resources: Resources,
|
||||
@DrawableRes backgroundId: Int,
|
||||
@DrawableRes buttonId: Int,
|
||||
private val buttonStateChangeListener: ButtonStateChangeListener,
|
||||
settings: InputOverlaySettings
|
||||
) :
|
||||
Input(settings) {
|
||||
var iconDpadUp: Drawable
|
||||
var iconDpadUpPressed: Drawable
|
||||
var iconDpadUpNotPressed: Drawable
|
||||
private val onButtonStateChange: (button: OverlayDpad, state: Boolean) -> Unit,
|
||||
private val alpha: Int,
|
||||
rect: Rect,
|
||||
) : Input(rect) {
|
||||
private val dpadUpDrawable = InputDrawable(resources, buttonId)
|
||||
private val dpadDownDrawable = InputDrawable(resources, buttonId)
|
||||
private val dpadLeftDrawable = InputDrawable(resources, buttonId)
|
||||
private val dpadRightDrawable = InputDrawable(resources, buttonId)
|
||||
private val background = ResourcesCompat.getDrawable(resources, backgroundId, null)!!
|
||||
|
||||
var iconDpadDown: Drawable
|
||||
var iconDpadDownPressed: Drawable
|
||||
var iconDpadDownNotPressed: Drawable
|
||||
private var dpadState: Int = NONE
|
||||
|
||||
var iconDpadLeft: Drawable
|
||||
var iconDpadLeftPressed: Drawable
|
||||
var iconDpadLeftNotPressed: Drawable
|
||||
|
||||
var iconDpadRight: Drawable
|
||||
var iconDpadRightPressed: Drawable
|
||||
var iconDpadRightNotPressed: Drawable
|
||||
var background: Drawable = ResourcesCompat.getDrawable(resources, backgroundId, null)!!
|
||||
|
||||
enum class DpadState {
|
||||
NONE, UP, DOWN, LEFT, RIGHT, UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT,
|
||||
}
|
||||
|
||||
var dpadState: DpadState = DpadState.NONE
|
||||
|
||||
var centreX: Int = 0
|
||||
var centreY: Int = 0
|
||||
var radius: Int = 0
|
||||
var currentPointerId: Int = -1
|
||||
private var centerX: Int = 0
|
||||
private var centerY: Int = 0
|
||||
private var radius2: Int = 0
|
||||
private var currentPointerId: Int = -1
|
||||
|
||||
override fun configure() {
|
||||
val rect = settings.rect
|
||||
this.centreX = rect.centerX()
|
||||
this.centreY = rect.centerY()
|
||||
this.radius = (min(rect.width(), rect.height()) / 2.0f).toInt()
|
||||
background.alpha = settings.alpha
|
||||
this.centerX = rect.centerX()
|
||||
this.centerY = rect.centerY()
|
||||
val radius = min(rect.width(), rect.height()) / 2
|
||||
radius2 = radius * radius
|
||||
background.alpha = alpha
|
||||
background.setBounds(
|
||||
centreX - radius,
|
||||
centreY - radius,
|
||||
centreX + radius,
|
||||
centreY + radius
|
||||
centerX - radius,
|
||||
centerY - radius,
|
||||
centerX + radius,
|
||||
centerY + radius
|
||||
)
|
||||
|
||||
val buttonSize = (radius * 0.5f).toInt()
|
||||
val configureButton =
|
||||
{ circleXPos: Int, circleYPos: Int, pressedButton: Drawable, notPressedButton: Drawable ->
|
||||
pressedButton.alpha =
|
||||
settings.alpha
|
||||
notPressedButton.alpha = settings.alpha
|
||||
val left = circleXPos - buttonSize / 2
|
||||
val top = circleYPos - buttonSize / 2
|
||||
pressedButton.setBounds(left, top, left + buttonSize, top + buttonSize)
|
||||
notPressedButton.setBounds(left, top, left + buttonSize, top + buttonSize)
|
||||
}
|
||||
val buttonSize = radius / 2
|
||||
val configureButton = { circleXPos: Int, circleYPos: Int, inputDrawable: InputDrawable ->
|
||||
inputDrawable.setAlpha(alpha)
|
||||
val left = circleXPos - buttonSize / 2
|
||||
val top = circleYPos - buttonSize / 2
|
||||
inputDrawable.setBounds(left, top, left + buttonSize, top + buttonSize)
|
||||
}
|
||||
configureButton(
|
||||
centreX,
|
||||
centreY - 2 * radius / 3,
|
||||
iconDpadUpPressed,
|
||||
iconDpadUp
|
||||
centerX,
|
||||
centerY - 2 * radius / 3,
|
||||
dpadUpDrawable
|
||||
)
|
||||
configureButton(
|
||||
centreX,
|
||||
centreY + 2 * radius / 3,
|
||||
iconDpadDownPressed,
|
||||
iconDpadDownNotPressed
|
||||
centerX,
|
||||
centerY + 2 * radius / 3,
|
||||
dpadDownDrawable
|
||||
)
|
||||
configureButton(
|
||||
centreX - 2 * radius / 3,
|
||||
centreY,
|
||||
iconDpadLeftPressed,
|
||||
iconDpadLeftNotPressed
|
||||
centerX - 2 * radius / 3,
|
||||
centerY,
|
||||
dpadLeftDrawable
|
||||
)
|
||||
configureButton(
|
||||
centreX + 2 * radius / 3,
|
||||
centreY,
|
||||
iconDpadRightPressed,
|
||||
iconDpadRightNotPressed
|
||||
centerX + 2 * radius / 3,
|
||||
centerY,
|
||||
dpadRightDrawable
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
val getNotPressedButtonIcon = {
|
||||
ResourcesCompat.getDrawable(resources, buttonId, null)!!
|
||||
}
|
||||
val getPressedButtonIcon = {
|
||||
ResourcesCompat.getDrawable(resources, buttonId, null)!!
|
||||
.applyInvertedColorTransform()
|
||||
}
|
||||
|
||||
iconDpadUpPressed = getPressedButtonIcon()
|
||||
iconDpadUpNotPressed = getNotPressedButtonIcon()
|
||||
iconDpadUp = iconDpadUpNotPressed
|
||||
|
||||
iconDpadDownPressed = getPressedButtonIcon()
|
||||
iconDpadDownNotPressed = getNotPressedButtonIcon()
|
||||
iconDpadDown = iconDpadDownNotPressed
|
||||
|
||||
iconDpadLeftPressed = getPressedButtonIcon()
|
||||
iconDpadLeftNotPressed = getNotPressedButtonIcon()
|
||||
iconDpadLeft = iconDpadLeftNotPressed
|
||||
|
||||
iconDpadRightPressed = getPressedButtonIcon()
|
||||
iconDpadRightNotPressed = getNotPressedButtonIcon()
|
||||
iconDpadRight = iconDpadRightNotPressed
|
||||
|
||||
configure()
|
||||
}
|
||||
|
||||
private fun updateState(nextDpadState: DpadState) {
|
||||
when (dpadState) {
|
||||
DpadState.NONE -> {}
|
||||
|
||||
DpadState.UP -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_UP,
|
||||
false
|
||||
)
|
||||
iconDpadUp = iconDpadUpNotPressed
|
||||
}
|
||||
|
||||
DpadState.DOWN -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_DOWN,
|
||||
false
|
||||
)
|
||||
iconDpadDown = iconDpadDownNotPressed
|
||||
}
|
||||
|
||||
DpadState.LEFT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_LEFT,
|
||||
false
|
||||
)
|
||||
iconDpadLeft = iconDpadLeftNotPressed
|
||||
}
|
||||
|
||||
DpadState.RIGHT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT,
|
||||
false
|
||||
)
|
||||
iconDpadRight = iconDpadRightNotPressed
|
||||
}
|
||||
|
||||
DpadState.UP_LEFT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_UP,
|
||||
false
|
||||
)
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_LEFT,
|
||||
false
|
||||
)
|
||||
iconDpadUp = iconDpadUpNotPressed
|
||||
iconDpadLeft = iconDpadLeftNotPressed
|
||||
}
|
||||
|
||||
DpadState.UP_RIGHT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_UP,
|
||||
false
|
||||
)
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT,
|
||||
false
|
||||
)
|
||||
iconDpadUp = iconDpadUpNotPressed
|
||||
iconDpadRight = iconDpadRightNotPressed
|
||||
}
|
||||
|
||||
DpadState.DOWN_LEFT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_DOWN,
|
||||
false
|
||||
)
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_LEFT,
|
||||
false
|
||||
)
|
||||
iconDpadDown = iconDpadDownNotPressed
|
||||
iconDpadLeft = iconDpadLeftNotPressed
|
||||
}
|
||||
|
||||
DpadState.DOWN_RIGHT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_DOWN,
|
||||
false
|
||||
)
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT,
|
||||
false
|
||||
)
|
||||
iconDpadDown = iconDpadDownNotPressed
|
||||
iconDpadRight = iconDpadRightNotPressed
|
||||
}
|
||||
}
|
||||
private fun updateState(nextDpadState: Int) {
|
||||
updateState(dpadState and nextDpadState.inv(), false)
|
||||
updateState(nextDpadState and dpadState.inv(), true)
|
||||
dpadState = nextDpadState
|
||||
when (nextDpadState) {
|
||||
DpadState.NONE -> {}
|
||||
DpadState.UP -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_UP,
|
||||
true
|
||||
)
|
||||
iconDpadUp = iconDpadUpPressed
|
||||
}
|
||||
}
|
||||
|
||||
DpadState.DOWN -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_DOWN,
|
||||
true
|
||||
)
|
||||
iconDpadDown = iconDpadDownPressed
|
||||
}
|
||||
private fun updateState(dpadState: Int, pressed: Boolean) {
|
||||
if (dpadState == NONE) return
|
||||
if ((dpadState and UP) != 0) {
|
||||
onButtonStateChange(OverlayDpad.DPAD_UP, pressed)
|
||||
dpadUpDrawable.setActiveState(pressed)
|
||||
}
|
||||
if ((dpadState and DOWN) != 0) {
|
||||
onButtonStateChange(OverlayDpad.DPAD_DOWN, pressed)
|
||||
dpadDownDrawable.setActiveState(pressed)
|
||||
}
|
||||
if ((dpadState and LEFT) != 0) {
|
||||
onButtonStateChange(OverlayDpad.DPAD_LEFT, pressed)
|
||||
dpadLeftDrawable.setActiveState(pressed)
|
||||
}
|
||||
if ((dpadState and RIGHT) != 0) {
|
||||
onButtonStateChange(OverlayDpad.DPAD_RIGHT, pressed)
|
||||
dpadRightDrawable.setActiveState(pressed)
|
||||
}
|
||||
}
|
||||
|
||||
DpadState.LEFT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_LEFT,
|
||||
true
|
||||
)
|
||||
iconDpadLeft = iconDpadLeftPressed
|
||||
}
|
||||
|
||||
DpadState.RIGHT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT,
|
||||
true
|
||||
)
|
||||
iconDpadRight = iconDpadRightPressed
|
||||
}
|
||||
|
||||
DpadState.UP_LEFT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_UP,
|
||||
true
|
||||
)
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_LEFT,
|
||||
true
|
||||
)
|
||||
iconDpadUp = iconDpadUpPressed
|
||||
iconDpadLeft = iconDpadLeftPressed
|
||||
}
|
||||
|
||||
DpadState.UP_RIGHT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_UP,
|
||||
true
|
||||
)
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT,
|
||||
true
|
||||
)
|
||||
iconDpadUp = iconDpadUpPressed
|
||||
iconDpadRight = iconDpadRightPressed
|
||||
}
|
||||
|
||||
DpadState.DOWN_LEFT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_DOWN,
|
||||
true
|
||||
)
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_LEFT,
|
||||
true
|
||||
)
|
||||
iconDpadDown = iconDpadDownPressed
|
||||
iconDpadLeft = iconDpadLeftPressed
|
||||
}
|
||||
|
||||
DpadState.DOWN_RIGHT -> {
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_DOWN,
|
||||
true
|
||||
)
|
||||
buttonStateChangeListener.onButtonStateChange(
|
||||
InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT,
|
||||
true
|
||||
)
|
||||
iconDpadDown = iconDpadDownPressed
|
||||
iconDpadRight = iconDpadRightPressed
|
||||
}
|
||||
private fun getStateByPosition(x: Int, y: Int): Int {
|
||||
val norm2 = (x - centerX) * (x - centerX) + (y - centerY) * (y - centerY)
|
||||
if (norm2 <= radius2 * 0.1f) {
|
||||
return NONE
|
||||
}
|
||||
val angle = atan2((y - centerY).toDouble(), (x - centerX).toDouble())
|
||||
return when (angle) {
|
||||
in -0.875 * Math.PI..<-0.625 * Math.PI -> UP or LEFT
|
||||
in -0.625 * Math.PI..<-0.375 * Math.PI -> UP
|
||||
in -0.375 * Math.PI..<-0.125 * Math.PI -> UP or RIGHT
|
||||
in -0.125 * Math.PI..<0.125 * Math.PI -> RIGHT
|
||||
in 0.125 * Math.PI..<0.375 * Math.PI -> DOWN or RIGHT
|
||||
in 0.375 * Math.PI..<0.625 * Math.PI -> DOWN
|
||||
in 0.625 * Math.PI..<0.875 * Math.PI -> DOWN or LEFT
|
||||
else -> LEFT // in [-pi, -0.875*pi) or [0.875*pi, pi]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,8 +130,7 @@ class DPadInput(
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
if (isInside(x, y)) {
|
||||
currentPointerId = pointerId
|
||||
val angle = atan2((y - centreY).toDouble(), (x - centreX).toDouble())
|
||||
nextState = getStateByAngle(angle)
|
||||
nextState = getStateByPosition(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +138,7 @@ class DPadInput(
|
||||
val pointerId = event.getPointerId(event.actionIndex)
|
||||
if (pointerId == currentPointerId) {
|
||||
currentPointerId = -1
|
||||
nextState = DpadState.NONE
|
||||
nextState = NONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,13 +152,7 @@ class DPadInput(
|
||||
}
|
||||
val x = event.getX(i).toInt()
|
||||
val y = event.getY(i).toInt()
|
||||
val norm2 = ((x - centreX) * (x - centreX) + (y - centreY) * (y - centreY))
|
||||
if (norm2 <= radius * radius * 0.1f) {
|
||||
nextState = DpadState.NONE
|
||||
break
|
||||
}
|
||||
val angle = atan2((y - centreY).toDouble(), (x - centreX).toDouble())
|
||||
nextState = getStateByAngle(angle)
|
||||
nextState = getStateByPosition(x, y)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -354,43 +165,26 @@ class DPadInput(
|
||||
}
|
||||
|
||||
override fun resetInput() {
|
||||
updateState(DpadState.NONE)
|
||||
}
|
||||
|
||||
private fun getStateByAngle(angle: Double): DpadState {
|
||||
if (-0.125 * Math.PI <= angle && angle < 0.125 * Math.PI) {
|
||||
return DpadState.RIGHT
|
||||
}
|
||||
if (0.125 * Math.PI <= angle && angle < 0.375 * Math.PI) {
|
||||
return DpadState.DOWN_RIGHT
|
||||
}
|
||||
if (0.375 * Math.PI <= angle && angle < 0.625 * Math.PI) {
|
||||
return DpadState.DOWN
|
||||
}
|
||||
if (0.625 * Math.PI <= angle && angle < 0.875 * Math.PI) {
|
||||
return DpadState.DOWN_LEFT
|
||||
}
|
||||
if (-0.875 * Math.PI <= angle && angle < -0.625 * Math.PI) {
|
||||
return DpadState.UP_LEFT
|
||||
}
|
||||
if (-0.625 * Math.PI <= angle && angle < -0.375 * Math.PI) {
|
||||
return DpadState.UP
|
||||
}
|
||||
if (-0.375 * Math.PI <= angle && angle < -0.125 * Math.PI) {
|
||||
return DpadState.UP_RIGHT
|
||||
}
|
||||
return DpadState.LEFT
|
||||
updateState(NONE)
|
||||
}
|
||||
|
||||
override fun isInside(x: Int, y: Int): Boolean {
|
||||
return ((x - centreX) * (x - centreX) + (y - centreY) * (y - centreY)) <= radius * radius
|
||||
return (x - centerX) * (x - centerX) + (y - centerY) * (y - centerY) <= radius2
|
||||
}
|
||||
|
||||
override fun drawInput(canvas: Canvas) {
|
||||
background.draw(canvas)
|
||||
iconDpadUp.draw(canvas)
|
||||
iconDpadDown.draw(canvas)
|
||||
iconDpadLeft.draw(canvas)
|
||||
iconDpadRight.draw(canvas)
|
||||
dpadUpDrawable.icon.draw(canvas)
|
||||
dpadDownDrawable.icon.draw(canvas)
|
||||
dpadLeftDrawable.icon.draw(canvas)
|
||||
dpadRightDrawable.icon.draw(canvas)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NONE = 0
|
||||
private const val UP = 1 shl 0
|
||||
private const val DOWN = 1 shl 1
|
||||
private const val LEFT = 1 shl 2
|
||||
private const val RIGHT = 1 shl 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,57 +5,55 @@ import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.view.MotionEvent
|
||||
import androidx.annotation.ColorInt
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsProvider.InputOverlaySettings
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
abstract class Input protected constructor(@JvmField protected val settings: InputOverlaySettings) {
|
||||
abstract class Input protected constructor(
|
||||
protected var rect: Rect,
|
||||
) {
|
||||
private var drawBoundingRectangle = false
|
||||
private var boundingRectangle: Rect
|
||||
private val boundingRectanglePaint = Paint()
|
||||
|
||||
init {
|
||||
boundingRectangle = settings.rect
|
||||
}
|
||||
fun getBoundingRectangle() = rect
|
||||
|
||||
abstract fun onTouch(event: MotionEvent): Boolean
|
||||
|
||||
fun moveInput(x: Int, y: Int, maxWidth: Int, maxHeight: Int) {
|
||||
val rect = settings.rect
|
||||
val width = rect.width()
|
||||
val height = rect.height()
|
||||
val left = min(
|
||||
max(x - width / 2.0f, 0.0f),
|
||||
(maxWidth - width).toFloat()
|
||||
).toInt()
|
||||
max(x - width / 2, 0),
|
||||
maxWidth - width
|
||||
)
|
||||
val top = min(
|
||||
max(y - height / 2.0f, 0.0f),
|
||||
(maxHeight - height).toFloat()
|
||||
).toInt()
|
||||
boundingRectangle = Rect(
|
||||
max(y - height / 2, 0),
|
||||
maxHeight - height
|
||||
)
|
||||
rect = Rect(
|
||||
left,
|
||||
top,
|
||||
left + width,
|
||||
top + height
|
||||
)
|
||||
settings.rect = boundingRectangle
|
||||
configure()
|
||||
}
|
||||
|
||||
fun resize(diffX: Int, diffY: Int, maxWidth: Int, maxHeight: Int, minWidthHeight: Int) {
|
||||
val rect = settings.rect
|
||||
val newRight = rect.right + diffX
|
||||
val newBottom = rect.bottom + diffY
|
||||
if (newRight - rect.left < minWidthHeight || newBottom - rect.top < minWidthHeight || newRight > maxWidth || newBottom > maxHeight) {
|
||||
if (newRight - rect.left < minWidthHeight
|
||||
|| newBottom - rect.top < minWidthHeight
|
||||
|| newRight > maxWidth
|
||||
|| newBottom > maxHeight
|
||||
) {
|
||||
return
|
||||
}
|
||||
boundingRectangle = Rect(
|
||||
rect = Rect(
|
||||
rect.left,
|
||||
rect.top,
|
||||
newRight,
|
||||
newBottom
|
||||
)
|
||||
settings.rect = boundingRectangle
|
||||
configure()
|
||||
}
|
||||
|
||||
@@ -65,17 +63,13 @@ abstract class Input protected constructor(@JvmField protected val settings: Inp
|
||||
drawBoundingRectangle = false
|
||||
}
|
||||
|
||||
fun saveConfiguration() {
|
||||
settings.saveSettings()
|
||||
}
|
||||
|
||||
protected abstract fun configure()
|
||||
|
||||
protected abstract fun drawInput(canvas: Canvas)
|
||||
|
||||
fun draw(canvas: Canvas) {
|
||||
if (drawBoundingRectangle) {
|
||||
canvas.drawRect(boundingRectangle, boundingRectanglePaint)
|
||||
canvas.drawRect(rect, boundingRectanglePaint)
|
||||
}
|
||||
drawInput(canvas)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package info.cemu.cemu.inputoverlay
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import info.cemu.cemu.drawable.getInvertedDrawable
|
||||
|
||||
class InputDrawable(
|
||||
resources: Resources,
|
||||
@DrawableRes inputDrawableId: Int,
|
||||
) {
|
||||
private val iconInactive: Drawable = ResourcesCompat.getDrawable(
|
||||
resources,
|
||||
inputDrawableId,
|
||||
null
|
||||
)!!
|
||||
private val iconActive: Drawable = iconInactive.getInvertedDrawable(resources)
|
||||
private var _icon = iconInactive
|
||||
val icon: Drawable
|
||||
get() = _icon
|
||||
|
||||
fun setActiveState(active: Boolean) {
|
||||
_icon = if (active) iconActive else iconInactive
|
||||
}
|
||||
|
||||
fun setAlpha(alpha: Int) {
|
||||
iconActive.alpha = alpha
|
||||
iconInactive.alpha = alpha
|
||||
}
|
||||
|
||||
fun setBounds(bounds: Rect) {
|
||||
iconActive.bounds = bounds
|
||||
iconInactive.bounds = bounds
|
||||
}
|
||||
|
||||
fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
setBounds(Rect(left, top, right, bottom))
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package info.cemu.cemu.inputoverlay
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
|
||||
data class InputConfig(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val alignEnd: Boolean,
|
||||
val alignBottom: Boolean,
|
||||
val paddingHorizontal: Int,
|
||||
val paddingVertical: Int,
|
||||
)
|
||||
|
||||
private fun String.toIntOrZero() = toIntOrNull() ?: 0
|
||||
|
||||
private const val INPUT_OVERLAY_CONFIG_TAG_NAME = "input-overlay-config"
|
||||
private const val WIDTH_CONFIG_TAG_NAME = "width"
|
||||
private const val SIZE_CONFIG_TAG_NAME = "size"
|
||||
private const val HEIGHT_CONFIG_TAG_NAME = "height"
|
||||
private const val ALIGN_END_CONFIG_TAG_NAME = "align-end"
|
||||
private const val ALIGN_BOTTOM_CONFIG_TAG_NAME = "align-bottom"
|
||||
private const val PADDING_HORIZONTAL_CONFIG_TAG_NAME = "padding-horizontal"
|
||||
private const val PADDING_VERTICAL_CONFIG_TAG_NAME = "padding-vertical"
|
||||
private const val NAME_CONFIG_TAG_NAME = "name"
|
||||
|
||||
private fun parseInputConfig(xmlPullParser: XmlPullParser): Pair<String, InputConfig>? {
|
||||
var name = ""
|
||||
var width = 0
|
||||
var height = 0
|
||||
var alignEnd = false
|
||||
var alignBottom = false
|
||||
var paddingHorizontal = 0
|
||||
var paddingVertical = 0
|
||||
var eventType = xmlPullParser.eventType
|
||||
var currentTag = ""
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
when (eventType) {
|
||||
XmlPullParser.END_TAG -> {
|
||||
if (xmlPullParser.name == INPUT_OVERLAY_CONFIG_TAG_NAME) {
|
||||
return if (name.isBlank()) null
|
||||
else name to InputConfig(
|
||||
width = width,
|
||||
height = height,
|
||||
alignEnd = alignEnd,
|
||||
alignBottom = alignBottom,
|
||||
paddingHorizontal = paddingHorizontal,
|
||||
paddingVertical = paddingVertical,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
XmlPullParser.START_TAG -> {
|
||||
currentTag = xmlPullParser.name
|
||||
}
|
||||
|
||||
XmlPullParser.TEXT -> {
|
||||
val text = xmlPullParser.text
|
||||
when (currentTag) {
|
||||
SIZE_CONFIG_TAG_NAME -> {
|
||||
val size = text.toIntOrZero()
|
||||
width = size
|
||||
height = size
|
||||
}
|
||||
|
||||
WIDTH_CONFIG_TAG_NAME -> width = text.toIntOrZero()
|
||||
HEIGHT_CONFIG_TAG_NAME -> height = text.toIntOrZero()
|
||||
ALIGN_END_CONFIG_TAG_NAME -> alignEnd = text.toBoolean()
|
||||
ALIGN_BOTTOM_CONFIG_TAG_NAME -> alignBottom = text.toBoolean()
|
||||
PADDING_HORIZONTAL_CONFIG_TAG_NAME -> paddingHorizontal = text.toIntOrZero()
|
||||
PADDING_VERTICAL_CONFIG_TAG_NAME -> paddingVertical = text.toIntOrZero()
|
||||
NAME_CONFIG_TAG_NAME -> name = text
|
||||
}
|
||||
}
|
||||
}
|
||||
eventType = xmlPullParser.next()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun parseDefaultInputConfigs(xmlPullParser: XmlPullParser): Map<String, InputConfig> {
|
||||
val inputConfigs = mutableMapOf<String, InputConfig>()
|
||||
var eventType = xmlPullParser.eventType
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG && xmlPullParser.name == INPUT_OVERLAY_CONFIG_TAG_NAME) {
|
||||
val inputConfig = parseInputConfig(xmlPullParser)
|
||||
if (inputConfig != null) {
|
||||
inputConfigs[inputConfig.first] = inputConfig.second
|
||||
}
|
||||
}
|
||||
eventType = xmlPullParser.next()
|
||||
}
|
||||
return inputConfigs
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package info.cemu.cemu.inputoverlay
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Rect
|
||||
import android.util.DisplayMetrics.DENSITY_DEFAULT
|
||||
import androidx.annotation.IntRange
|
||||
import info.cemu.cemu.R
|
||||
import info.cemu.cemu.nativeinterface.NativeInput
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class OverlaySettings(
|
||||
var isVibrateOnTouchEnabled: Boolean,
|
||||
var isOverlayEnabled: Boolean,
|
||||
@IntRange(0, (NativeInput.MAX_CONTROLLERS - 1L))
|
||||
var controllerIndex: Int,
|
||||
@IntRange(0, 255)
|
||||
var alpha: Int,
|
||||
) {
|
||||
companion object {
|
||||
private const val DEFAULT_ALPHA: Int = 64
|
||||
private const val IS_VIBRATE_ON_TOUCH_ENABLED_KEY = "IS_VIBRATE_ON_TOUCH_ENABLED"
|
||||
private const val IS_OVERLAY_ENABLED_KEY = "IS_OVERLAY_ENABLED"
|
||||
private const val CONTROLLER_INDEX_KEY = "CONTROLLER_INDEX"
|
||||
private const val ALPHA_KEY = "ALPHA"
|
||||
}
|
||||
|
||||
internal constructor(sharedPreferences: SharedPreferences) : this(
|
||||
isVibrateOnTouchEnabled = sharedPreferences.getBoolean(
|
||||
IS_VIBRATE_ON_TOUCH_ENABLED_KEY,
|
||||
false
|
||||
),
|
||||
isOverlayEnabled = sharedPreferences.getBoolean(IS_OVERLAY_ENABLED_KEY, false),
|
||||
controllerIndex = sharedPreferences.getInt(CONTROLLER_INDEX_KEY, 0),
|
||||
alpha = sharedPreferences.getInt(ALPHA_KEY, DEFAULT_ALPHA),
|
||||
)
|
||||
|
||||
internal fun save(sharedPreferences: SharedPreferences) {
|
||||
sharedPreferences.edit().apply {
|
||||
putBoolean(IS_VIBRATE_ON_TOUCH_ENABLED_KEY, isVibrateOnTouchEnabled)
|
||||
putBoolean(IS_OVERLAY_ENABLED_KEY, isOverlayEnabled)
|
||||
putInt(CONTROLLER_INDEX_KEY, controllerIndex)
|
||||
putInt(ALPHA_KEY, alpha)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InputOverlaySettingsManager(context: Context) {
|
||||
private val defaultInputConfigs =
|
||||
parseDefaultInputConfigs(context.resources.getXml(R.xml.input_overlay_default_configs))
|
||||
private val sharedPreferences: SharedPreferences =
|
||||
context.getSharedPreferences(INPUT_OVERLAY_SETTINGS_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
var overlaySettings: OverlaySettings
|
||||
get() = OverlaySettings(sharedPreferences)
|
||||
set(value) = value.save(sharedPreferences)
|
||||
|
||||
fun getInputOverlayRectangle(
|
||||
input: OverlayInput,
|
||||
width: Int,
|
||||
height: Int,
|
||||
density: Int,
|
||||
): Rect {
|
||||
return getRectangle(input) ?: getDefaultRectangle(input, width, height, density)
|
||||
}
|
||||
|
||||
private fun getRectLeftConfigName(input: OverlayInput) = "${input.configName}_LEFT"
|
||||
private fun getRectTopConfigName(input: OverlayInput) = "${input.configName}_TOP"
|
||||
private fun getRectRightConfigName(input: OverlayInput) = "${input.configName}_RIGHT"
|
||||
private fun getRectBottomConfigName(input: OverlayInput) = "${input.configName}_BOTTOM"
|
||||
|
||||
private fun getRectangle(input: OverlayInput): Rect? {
|
||||
val left = sharedPreferences.getInt(getRectLeftConfigName(input), -1)
|
||||
val top = sharedPreferences.getInt(getRectTopConfigName(input), -1)
|
||||
val right = sharedPreferences.getInt(getRectRightConfigName(input), -1)
|
||||
val bottom = sharedPreferences.getInt(getRectBottomConfigName(input), -1)
|
||||
if (left == -1 || top == -1 || right == -1 || bottom == -1) {
|
||||
return null
|
||||
}
|
||||
return Rect(left, top, right, bottom)
|
||||
}
|
||||
|
||||
fun saveRectangle(input: OverlayInput, rect: Rect) {
|
||||
sharedPreferences.edit().apply {
|
||||
putInt(getRectLeftConfigName(input), rect.left)
|
||||
putInt(getRectTopConfigName(input), rect.top)
|
||||
putInt(getRectRightConfigName(input), rect.right)
|
||||
putInt(getRectBottomConfigName(input), rect.bottom)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun clearSavedRectangle(input: OverlayInput) {
|
||||
sharedPreferences.edit().apply {
|
||||
remove(getRectLeftConfigName(input))
|
||||
remove(getRectTopConfigName(input))
|
||||
remove(getRectRightConfigName(input))
|
||||
remove(getRectBottomConfigName(input))
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultRectangle(
|
||||
input: OverlayInput,
|
||||
width: Int,
|
||||
height: Int,
|
||||
density: Int,
|
||||
): Rect {
|
||||
fun Int.dpToPx() = (this * density) / DENSITY_DEFAULT
|
||||
val inputConfig = defaultInputConfigs[input.configName] ?: return Rect()
|
||||
val inputWidth = inputConfig.width.dpToPx()
|
||||
val horizontalPadding = inputConfig.paddingHorizontal.dpToPx()
|
||||
val verticalPadding = inputConfig.paddingVertical.dpToPx()
|
||||
val inputHeight = inputConfig.height.dpToPx()
|
||||
val top = min(
|
||||
max(if (inputConfig.alignBottom) height - inputHeight else 0, verticalPadding),
|
||||
height - verticalPadding - inputHeight
|
||||
)
|
||||
val left = min(
|
||||
max(if (inputConfig.alignEnd) width - inputWidth else 0, horizontalPadding),
|
||||
width - horizontalPadding - inputWidth
|
||||
)
|
||||
val right = left + inputWidth
|
||||
val bottom = top + inputHeight
|
||||
return Rect(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val INPUT_OVERLAY_SETTINGS_NAME = "INPUT_OVERLAY_SETTINGS"
|
||||
}
|
||||
}
|
||||
-306
@@ -1,306 +0,0 @@
|
||||
package info.cemu.cemu.inputoverlay
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Rect
|
||||
import androidx.annotation.IntRange
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.MutablePreferences
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import info.cemu.cemu.CemuApplication
|
||||
import info.cemu.cemu.nativeinterface.NativeInput
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.util.Optional
|
||||
|
||||
private const val INPUT_DATA_STORE_NAME = "input-settings22"
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = INPUT_DATA_STORE_NAME)
|
||||
private fun <T> Preferences.get(key: Preferences.Key<T>, default: T) =
|
||||
this[key] ?: default
|
||||
|
||||
class OverlaySettings(
|
||||
var isVibrateOnTouchEnabled: Boolean,
|
||||
var isOverlayEnabled: Boolean,
|
||||
@IntRange(0, (NativeInput.MAX_CONTROLLERS - 1L))
|
||||
var controllerIndex: Int,
|
||||
@IntRange(0, 255)
|
||||
var alpha: Int,
|
||||
) {
|
||||
companion object {
|
||||
private const val DEFAULT_ALPHA: Int = 64
|
||||
private const val IS_VIBRATE_ON_TOUCH_ENABLED_KEY = "IS_VIBRATE_ON_TOUCH_ENABLED"
|
||||
private const val IS_OVERLAY_ENABLED_KEY = "IS_OVERLAY_ENABLED"
|
||||
private const val CONTROLLER_INDEX_KEY = "CONTROLLER_INDEX"
|
||||
private const val ALPHA_KEY = "ALPHA"
|
||||
}
|
||||
|
||||
internal constructor(sharedPreferences: SharedPreferences) : this(
|
||||
isVibrateOnTouchEnabled = sharedPreferences.getBoolean(
|
||||
IS_VIBRATE_ON_TOUCH_ENABLED_KEY,
|
||||
false
|
||||
),
|
||||
isOverlayEnabled = sharedPreferences.getBoolean(IS_OVERLAY_ENABLED_KEY, false),
|
||||
controllerIndex = sharedPreferences.getInt(CONTROLLER_INDEX_KEY, 0),
|
||||
alpha = sharedPreferences.getInt(ALPHA_KEY, DEFAULT_ALPHA),
|
||||
)
|
||||
|
||||
internal fun save(sharedPreferences: SharedPreferences) {
|
||||
sharedPreferences.edit().apply {
|
||||
putBoolean(IS_VIBRATE_ON_TOUCH_ENABLED_KEY, isVibrateOnTouchEnabled)
|
||||
putBoolean(IS_OVERLAY_ENABLED_KEY, isOverlayEnabled)
|
||||
putInt(CONTROLLER_INDEX_KEY, controllerIndex)
|
||||
putInt(ALPHA_KEY, alpha)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InputOverlaySettingsProvider(context: Context) {
|
||||
enum class OverlayInput {
|
||||
A,
|
||||
B,
|
||||
ONE,
|
||||
TWO,
|
||||
C,
|
||||
Z,
|
||||
HOME,
|
||||
DPAD,
|
||||
L,
|
||||
L_STICK,
|
||||
MINUS,
|
||||
PLUS,
|
||||
R,
|
||||
R_STICK,
|
||||
X,
|
||||
Y,
|
||||
ZL,
|
||||
ZR,
|
||||
NUN_CHUCK_AXIS,
|
||||
LEFT_AXIS,
|
||||
RIGHT_AXIS,
|
||||
}
|
||||
|
||||
val sharedPreferences: SharedPreferences =
|
||||
context.getSharedPreferences("input-overlay-settings", Context.MODE_PRIVATE);
|
||||
|
||||
var overlaySettings: OverlaySettings
|
||||
get() = OverlaySettings(sharedPreferences)
|
||||
set(value) = value.save(sharedPreferences)
|
||||
|
||||
|
||||
// private val sharedPreferences: SharedPreferences =
|
||||
// context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
fun getOverlaySettingsForInput(
|
||||
input: OverlayInput,
|
||||
width: Int,
|
||||
height: Int
|
||||
): InputOverlaySettings {
|
||||
return InputOverlaySettings(
|
||||
getRectangle(input).orElseGet({ getDefaultRectangle(input, width, height) }),
|
||||
100,
|
||||
{ settings: InputOverlaySettings ->
|
||||
// val editor: SharedPreferences.Editor = sharedPreferences.edit()
|
||||
// val rect: Rect? = settings.rect
|
||||
// editor.putInt(input.toString() + "left", rect!!.left)
|
||||
// editor.putInt(input.toString() + "top", rect.top)
|
||||
// editor.putInt(input.toString() + "right", rect.right)
|
||||
// editor.putInt(input.toString() + "bottom", rect.bottom)
|
||||
// editor.putInt(input.toString() + "alpha", settings.alpha)
|
||||
// editor.apply()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun getRectangle(input: OverlayInput): Optional<Rect> {
|
||||
// val left: Int = sharedPreferences.getInt(input.toString() + "left", -1)
|
||||
// val top: Int = sharedPreferences.getInt(input.toString() + "top", -1)
|
||||
// val right: Int = sharedPreferences.getInt(input.toString() + "right", -1)
|
||||
// val bottom: Int = sharedPreferences.getInt(input.toString() + "bottom", -1)
|
||||
// if (left == -1 || top == -1 || right == -1 || bottom == -1) {
|
||||
// }
|
||||
return Optional.empty()
|
||||
// return Optional.of(Rect(left, top, right, bottom))
|
||||
}
|
||||
|
||||
fun getDefaultRectangle(input: OverlayInput, width: Int, height: Int): Rect {
|
||||
val pmButtonRadius: Int = (height * 0.065f).toInt()
|
||||
val pmButtonsCentreX: Int = (width * 0.5f).toInt()
|
||||
val pmButtonsCentreY: Int = (height * 0.875f).toInt()
|
||||
|
||||
val roundButtonRadius: Int = (height * 0.065f).toInt()
|
||||
val abxyButtonsCentreX: Int = width - roundButtonRadius * 4
|
||||
val abxyButtonsCentreY: Int = height - roundButtonRadius * 4
|
||||
|
||||
val rectangleButtonsHeight: Int = (height * 0.10f).toInt()
|
||||
val rectangleButtonsWidth: Int = rectangleButtonsHeight * 2
|
||||
|
||||
val triggersLLeft: Int = (width * 0.05f).toInt()
|
||||
val triggersRLeft: Int = (width * 0.95f - rectangleButtonsWidth).toInt()
|
||||
val triggersTop: Int = (height * 0.05f).toInt()
|
||||
|
||||
val dpadRadius: Int = (height * 0.20f).toInt()
|
||||
val dpadCentreX: Int = dpadRadius
|
||||
val dpadCentreY: Int = (height * 0.75f).toInt()
|
||||
|
||||
val joystickRadius: Int = (height * 0.10f).toInt()
|
||||
val rightJoystickCentreX: Int = (width * 0.75f).toInt()
|
||||
val leftJoystickCentreX: Int = (width * 0.25f).toInt()
|
||||
val joystickCentreY: Int = (height * 0.55f).toInt()
|
||||
val joystickClickRadius: Int = (joystickRadius * 0.7f).toInt()
|
||||
// TODO: move this to res?
|
||||
return when (input) {
|
||||
OverlayInput.A -> Rect(
|
||||
abxyButtonsCentreX + roundButtonRadius,
|
||||
abxyButtonsCentreY - roundButtonRadius,
|
||||
abxyButtonsCentreX + roundButtonRadius * 3,
|
||||
abxyButtonsCentreY + roundButtonRadius
|
||||
)
|
||||
|
||||
OverlayInput.B -> Rect(
|
||||
abxyButtonsCentreX - roundButtonRadius,
|
||||
abxyButtonsCentreY + roundButtonRadius,
|
||||
abxyButtonsCentreX + roundButtonRadius,
|
||||
abxyButtonsCentreY + roundButtonRadius * 3
|
||||
)
|
||||
|
||||
OverlayInput.X, OverlayInput.ONE -> Rect(
|
||||
abxyButtonsCentreX - roundButtonRadius,
|
||||
abxyButtonsCentreY - roundButtonRadius * 3,
|
||||
abxyButtonsCentreX + roundButtonRadius,
|
||||
abxyButtonsCentreY - roundButtonRadius
|
||||
)
|
||||
|
||||
OverlayInput.Y, OverlayInput.TWO -> Rect(
|
||||
abxyButtonsCentreX - roundButtonRadius * 3,
|
||||
abxyButtonsCentreY - roundButtonRadius,
|
||||
abxyButtonsCentreX - roundButtonRadius,
|
||||
abxyButtonsCentreY + roundButtonRadius
|
||||
)
|
||||
|
||||
OverlayInput.MINUS -> Rect(
|
||||
pmButtonsCentreX - pmButtonRadius * 3,
|
||||
pmButtonsCentreY - pmButtonRadius,
|
||||
pmButtonsCentreX - pmButtonRadius,
|
||||
pmButtonsCentreY + pmButtonRadius
|
||||
)
|
||||
|
||||
OverlayInput.PLUS -> Rect(
|
||||
pmButtonsCentreX + pmButtonRadius,
|
||||
pmButtonsCentreY - pmButtonRadius,
|
||||
pmButtonsCentreX + pmButtonRadius * 3,
|
||||
pmButtonsCentreY + pmButtonRadius
|
||||
)
|
||||
|
||||
OverlayInput.L -> Rect(
|
||||
triggersLLeft,
|
||||
triggersTop,
|
||||
triggersLLeft + rectangleButtonsWidth,
|
||||
triggersTop + rectangleButtonsHeight
|
||||
)
|
||||
|
||||
OverlayInput.ZL -> Rect(
|
||||
triggersLLeft,
|
||||
(triggersTop + rectangleButtonsHeight * 1.5f).toInt(),
|
||||
triggersLLeft + rectangleButtonsWidth,
|
||||
(triggersTop + rectangleButtonsHeight * 2.5f).toInt()
|
||||
)
|
||||
|
||||
OverlayInput.R -> Rect(
|
||||
triggersRLeft,
|
||||
triggersTop,
|
||||
triggersRLeft + rectangleButtonsWidth,
|
||||
triggersTop + rectangleButtonsHeight
|
||||
)
|
||||
|
||||
OverlayInput.ZR -> Rect(
|
||||
triggersRLeft,
|
||||
(triggersTop + rectangleButtonsHeight * 1.5f).toInt(),
|
||||
triggersRLeft + rectangleButtonsWidth,
|
||||
(triggersTop + rectangleButtonsHeight * 2.5f).toInt()
|
||||
)
|
||||
|
||||
OverlayInput.C, OverlayInput.HOME, OverlayInput.Z, OverlayInput.NUN_CHUCK_AXIS -> Rect()
|
||||
OverlayInput.DPAD -> Rect(
|
||||
dpadCentreX - dpadRadius,
|
||||
dpadCentreY - dpadRadius,
|
||||
dpadCentreX + dpadRadius,
|
||||
dpadCentreY + dpadRadius
|
||||
)
|
||||
|
||||
OverlayInput.LEFT_AXIS -> Rect(
|
||||
leftJoystickCentreX - joystickRadius,
|
||||
joystickCentreY - joystickRadius,
|
||||
leftJoystickCentreX + joystickRadius,
|
||||
joystickCentreY + joystickRadius
|
||||
)
|
||||
|
||||
OverlayInput.RIGHT_AXIS -> Rect(
|
||||
rightJoystickCentreX - joystickRadius,
|
||||
joystickCentreY - joystickRadius,
|
||||
rightJoystickCentreX + joystickRadius,
|
||||
joystickCentreY + joystickRadius
|
||||
)
|
||||
|
||||
OverlayInput.L_STICK -> Rect(
|
||||
(leftJoystickCentreX + joystickRadius * 1.5f - joystickClickRadius).toInt(),
|
||||
(joystickCentreY + joystickRadius * 1.5f - joystickClickRadius).toInt(),
|
||||
(leftJoystickCentreX + joystickRadius * 1.5f + joystickClickRadius).toInt(),
|
||||
(joystickCentreY + joystickRadius * 1.5f + joystickClickRadius).toInt()
|
||||
)
|
||||
|
||||
OverlayInput.R_STICK -> Rect(
|
||||
(rightJoystickCentreX - joystickRadius * 1.5f - joystickClickRadius).toInt(),
|
||||
(joystickCentreY + joystickRadius * 1.5f - joystickClickRadius).toInt(),
|
||||
(rightJoystickCentreX - joystickRadius * 1.5f + joystickClickRadius).toInt(),
|
||||
(joystickCentreY + joystickRadius * 1.5f + joystickClickRadius).toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// class OverlaySettings internal constructor(
|
||||
// var isVibrateOnTouchEnabled: Boolean,
|
||||
// var isOverlayEnabled: Boolean,
|
||||
// controllerIndex: Int,
|
||||
// alpha: Int,
|
||||
// private val overlaySettingsConsumer: (OverlaySettings) -> Unit
|
||||
// ) {
|
||||
// var controllerIndex: Int = 0
|
||||
// set(controllerIndex) {
|
||||
// require(controllerIndex in 0..<NativeInput.MAX_CONTROLLERS) { "Invalid controller index $controllerIndex" }
|
||||
// field = controllerIndex
|
||||
// }
|
||||
// var alpha: Int = 0
|
||||
// set(alpha) {
|
||||
// field = max(0, min(255, alpha))
|
||||
// }
|
||||
//
|
||||
//
|
||||
// init {
|
||||
// this.controllerIndex = controllerIndex
|
||||
// this.alpha = alpha
|
||||
// }
|
||||
//
|
||||
// fun saveSettings() {
|
||||
// overlaySettingsConsumer(this)
|
||||
// }
|
||||
// }
|
||||
|
||||
class InputOverlaySettings(
|
||||
var rect: Rect,
|
||||
var alpha: Int,
|
||||
val inputOverlaySettingsConsumer: (InputOverlaySettings) -> Unit
|
||||
) {
|
||||
fun saveSettings() {
|
||||
inputOverlaySettingsConsumer(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+226
-258
File diff suppressed because it is too large
Load Diff
@@ -2,13 +2,11 @@ package info.cemu.cemu.inputoverlay
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.MotionEvent
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import info.cemu.cemu.drawable.getInvertedDrawable
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsProvider.InputOverlaySettings
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySurfaceView.OverlayJoystick
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sqrt
|
||||
|
||||
@@ -16,17 +14,13 @@ class Joystick(
|
||||
resources: Resources,
|
||||
@DrawableRes joystickBackgroundId: Int,
|
||||
@DrawableRes innerStickId: Int,
|
||||
stickStateChangeListener: StickStateChangeListener,
|
||||
joystick: OverlayJoystick,
|
||||
settings: InputOverlaySettings
|
||||
) :
|
||||
Input(settings) {
|
||||
private val iconPressed: Drawable
|
||||
private val iconNotPressed: Drawable =
|
||||
ResourcesCompat.getDrawable(resources, innerStickId, null)!!
|
||||
private val onStickStateChange: (x: Float, y: Float) -> Unit,
|
||||
private val alpha: Int,
|
||||
boundingRectangle: Rect,
|
||||
) : Input(boundingRectangle) {
|
||||
private val joystickBackground: Drawable =
|
||||
ResourcesCompat.getDrawable(resources, joystickBackgroundId, null)!!
|
||||
private var icon: Drawable
|
||||
private val joystickDrawable = InputDrawable(resources, innerStickId)
|
||||
private var currentPointerId = -1
|
||||
private var centerX = 0
|
||||
private var centerY = 0
|
||||
@@ -35,31 +29,16 @@ class Joystick(
|
||||
private var radius = 0
|
||||
private var innerRadius = 0
|
||||
|
||||
fun interface StickStateChangeListener {
|
||||
fun onStickStateChange(joystick: OverlayJoystick, x: Float, y: Float)
|
||||
}
|
||||
|
||||
private val stickStateChangeListener: StickStateChangeListener
|
||||
private val joystick: OverlayJoystick
|
||||
|
||||
init {
|
||||
iconPressed = iconNotPressed.getInvertedDrawable(resources)
|
||||
icon = iconNotPressed
|
||||
this.stickStateChangeListener = stickStateChangeListener
|
||||
this.joystick = joystick
|
||||
configure()
|
||||
}
|
||||
|
||||
private fun updateState(pressed: Boolean, x: Float, y: Float) {
|
||||
icon = if (pressed) {
|
||||
iconPressed
|
||||
} else {
|
||||
iconNotPressed
|
||||
}
|
||||
stickStateChangeListener.onStickStateChange(joystick, x, y)
|
||||
joystickDrawable.setActiveState(pressed)
|
||||
onStickStateChange(x, y)
|
||||
val newCentreX = (centerX + radius * x).toInt()
|
||||
val newCentreY = (centerY + radius * y).toInt()
|
||||
iconPressed.setBounds(
|
||||
joystickDrawable.setBounds(
|
||||
newCentreX - innerRadius,
|
||||
newCentreY - innerRadius,
|
||||
newCentreX + innerRadius,
|
||||
@@ -68,33 +47,21 @@ class Joystick(
|
||||
}
|
||||
|
||||
override fun configure() {
|
||||
val joystickBounds = settings.rect
|
||||
this.centerX = joystickBounds.centerX()
|
||||
this.originalCenterX = this.centerX
|
||||
this.centerY = joystickBounds.centerY()
|
||||
this.originalCenterY = this.centerY
|
||||
this.radius =
|
||||
(min(
|
||||
joystickBounds.width().toDouble(),
|
||||
joystickBounds.height().toDouble()
|
||||
) / 2).toInt()
|
||||
this.innerRadius = (radius * 0.65f).toInt()
|
||||
centerX = rect.centerX()
|
||||
originalCenterX = centerX
|
||||
centerY = rect.centerY()
|
||||
originalCenterY = centerY
|
||||
radius = min(rect.width(), rect.height()) / 2
|
||||
innerRadius = (radius * 0.65f).toInt()
|
||||
joystickBackground.setBounds(
|
||||
centerX - radius,
|
||||
centerY - radius,
|
||||
centerX + radius,
|
||||
centerY + radius
|
||||
)
|
||||
joystickBackground.alpha = settings.alpha
|
||||
iconPressed.alpha = settings.alpha
|
||||
iconPressed.setBounds(
|
||||
centerX - innerRadius,
|
||||
centerY - innerRadius,
|
||||
centerX + innerRadius,
|
||||
centerY + innerRadius
|
||||
)
|
||||
iconNotPressed.alpha = settings.alpha
|
||||
iconNotPressed.setBounds(
|
||||
joystickBackground.alpha = alpha
|
||||
joystickDrawable.setAlpha(alpha)
|
||||
joystickDrawable.setBounds(
|
||||
centerX - innerRadius,
|
||||
centerY - innerRadius,
|
||||
centerX + innerRadius,
|
||||
@@ -103,7 +70,6 @@ class Joystick(
|
||||
}
|
||||
|
||||
override fun onTouch(event: MotionEvent): Boolean {
|
||||
var stateUpdated = false
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
val pointerIndex = event.actionIndex
|
||||
@@ -120,7 +86,7 @@ class Joystick(
|
||||
)
|
||||
currentPointerId = event.getPointerId(pointerIndex)
|
||||
updateState(true, 0.0f, 0.0f)
|
||||
stateUpdated = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +102,7 @@ class Joystick(
|
||||
)
|
||||
currentPointerId = -1
|
||||
updateState(false, 0.0f, 0.0f)
|
||||
stateUpdated = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,12 +122,11 @@ class Joystick(
|
||||
y /= norm
|
||||
}
|
||||
updateState(true, x, y)
|
||||
stateUpdated = true
|
||||
break
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return stateUpdated
|
||||
return false
|
||||
}
|
||||
|
||||
override fun resetInput() {
|
||||
@@ -170,7 +135,7 @@ class Joystick(
|
||||
|
||||
override fun drawInput(canvas: Canvas) {
|
||||
joystickBackground.draw(canvas)
|
||||
icon.draw(canvas)
|
||||
joystickDrawable.icon.draw(canvas)
|
||||
}
|
||||
|
||||
override fun isInside(x: Int, y: Int): Boolean {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package info.cemu.cemu.inputoverlay
|
||||
|
||||
sealed interface OverlayInput {
|
||||
val configName: String
|
||||
}
|
||||
|
||||
enum class OverlayJoystick : OverlayInput {
|
||||
LEFT,
|
||||
RIGHT;
|
||||
|
||||
override val configName = "AXIS_$name"
|
||||
}
|
||||
|
||||
enum class OverlayButton : OverlayInput {
|
||||
A,
|
||||
B,
|
||||
ONE,
|
||||
TWO,
|
||||
C,
|
||||
Z,
|
||||
HOME,
|
||||
L,
|
||||
L_STICK_CLICK,
|
||||
MINUS,
|
||||
PLUS,
|
||||
R,
|
||||
R_STICK_CLICK,
|
||||
X,
|
||||
Y,
|
||||
ZL,
|
||||
ZR;
|
||||
|
||||
override val configName = "BUTTON_$name"
|
||||
}
|
||||
|
||||
enum class OverlayDpad : OverlayInput {
|
||||
DPAD_DOWN,
|
||||
DPAD_LEFT,
|
||||
DPAD_RIGHT,
|
||||
DPAD_UP;
|
||||
|
||||
override val configName = "DPAD"
|
||||
}
|
||||
|
||||
val OverlayInputList: List<OverlayInput> =
|
||||
OverlayButton.entries + OverlayJoystick.entries + OverlayDpad.entries
|
||||
@@ -1,18 +1,16 @@
|
||||
package info.cemu.cemu.inputoverlay
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import androidx.annotation.DrawableRes
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsProvider.InputOverlaySettings
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySurfaceView.OverlayButton
|
||||
|
||||
class RectangleButton(
|
||||
resources: Resources,
|
||||
@DrawableRes buttonId: Int,
|
||||
buttonStateChangeListener: ButtonStateChangeListener,
|
||||
overlayButton: OverlayButton,
|
||||
settings: InputOverlaySettings
|
||||
) :
|
||||
Button(resources, buttonId, buttonStateChangeListener, overlayButton, settings) {
|
||||
onButtonStateChange: (state: Boolean) -> Unit,
|
||||
private val alpha: Int,
|
||||
rect: Rect,
|
||||
) : Button(resources, buttonId, onButtonStateChange, rect) {
|
||||
var left: Int = 0
|
||||
var top: Int = 0
|
||||
var right: Int = 0
|
||||
@@ -23,13 +21,12 @@ class RectangleButton(
|
||||
}
|
||||
|
||||
override fun configure() {
|
||||
val rect = settings.rect
|
||||
this.left = rect.left
|
||||
this.top = rect.top
|
||||
this.right = rect.right
|
||||
this.bottom = rect.bottom
|
||||
iconPressed.setBounds(left, top, right, bottom)
|
||||
iconNotPressed.setBounds(left, top, right, bottom)
|
||||
inputDrawable.setAlpha(alpha)
|
||||
inputDrawable.setBounds(left, top, right, bottom)
|
||||
}
|
||||
|
||||
override fun isInside(x: Int, y: Int): Boolean {
|
||||
|
||||
@@ -3,42 +3,38 @@ package info.cemu.cemu.inputoverlay
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import androidx.annotation.DrawableRes
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsProvider.InputOverlaySettings
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySurfaceView.OverlayButton
|
||||
import kotlin.math.min
|
||||
|
||||
class RoundButton internal constructor(
|
||||
resources: Resources,
|
||||
@DrawableRes buttonId: Int,
|
||||
buttonStateChangeListener: ButtonStateChangeListener,
|
||||
overlayButton: OverlayButton,
|
||||
settings: InputOverlaySettings
|
||||
) :
|
||||
Button(resources, buttonId, buttonStateChangeListener, overlayButton, settings) {
|
||||
var centreX: Int = 0
|
||||
var centreY: Int = 0
|
||||
var radius: Int = 0
|
||||
onButtonStateChange: (state: Boolean) -> Unit,
|
||||
private val alpha: Int,
|
||||
rect: Rect,
|
||||
) : Button(resources, buttonId, onButtonStateChange, rect) {
|
||||
private var centreX: Int = 0
|
||||
private var centreY: Int = 0
|
||||
private var radius2: Int = 0
|
||||
|
||||
init {
|
||||
configure()
|
||||
}
|
||||
|
||||
override fun configure() {
|
||||
val rect = settings.rect
|
||||
centreX = rect.centerX()
|
||||
centreY = rect.centerY()
|
||||
radius = (min(rect.width().toDouble(), rect.height().toDouble()) / 2).toInt()
|
||||
val iconRect = Rect(
|
||||
val radius = min(rect.width(), rect.height()) / 2
|
||||
inputDrawable.setBounds(
|
||||
centreX - radius,
|
||||
centreY - radius,
|
||||
centreX + radius,
|
||||
centreY + radius
|
||||
)
|
||||
iconPressed.bounds = iconRect
|
||||
iconNotPressed.bounds = iconRect
|
||||
inputDrawable.setAlpha(alpha)
|
||||
radius2 = radius * radius
|
||||
}
|
||||
|
||||
override fun isInside(x: Int, y: Int): Boolean {
|
||||
return ((x - centreX) * (x - centreX) + (y - centreY) * (y - centreY)) <= radius * radius
|
||||
return ((x - centreX) * (x - centreX) + (y - centreY) * (y - centreY)) <= radius2
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -5,23 +5,23 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import info.cemu.cemu.CemuApplication
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsProvider
|
||||
import info.cemu.cemu.inputoverlay.InputOverlaySettingsManager
|
||||
|
||||
class InputOverlaySettingsViewModel(
|
||||
private val inputOverlaySettingsProvider: InputOverlaySettingsProvider
|
||||
private val inputOverlaySettingsManager: InputOverlaySettingsManager
|
||||
) : ViewModel() {
|
||||
val overlaySettings = inputOverlaySettingsProvider.overlaySettings
|
||||
val overlaySettings = inputOverlaySettingsManager.overlaySettings
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
inputOverlaySettingsProvider.overlaySettings = overlaySettings
|
||||
inputOverlaySettingsManager.overlaySettings = overlaySettings
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
InputOverlaySettingsViewModel(
|
||||
InputOverlaySettingsProvider(CemuApplication.Application)
|
||||
InputOverlaySettingsManager(CemuApplication.Application)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="95.036dp"
|
||||
android:height="95.036dp"
|
||||
android:viewportWidth="95.036"
|
||||
android:viewportHeight="95.036">
|
||||
android:width="94.033dp"
|
||||
android:height="53.792dp"
|
||||
android:viewportWidth="94.033"
|
||||
android:viewportHeight="53.792">
|
||||
<path
|
||||
android:pathData="M47.518,47.518m-47.268,0a47.268,47.268 0,1 1,94.536 0a47.268,47.268 0,1 1,-94.536 0"
|
||||
android:pathData="M6.828,0.25L87.204,0.25A6.578,6.578 0,0 1,93.783 6.828L93.783,46.964A6.578,6.578 0,0 1,87.204 53.542L6.828,53.542A6.578,6.578 0,0 1,0.25 46.964L0.25,6.828A6.578,6.578 0,0 1,6.828 0.25z"
|
||||
android:strokeWidth="0.5"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:fillType="evenOdd" />
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="m29.381,70.075l36.274,0l0,-4.68l-31.203,0l0.78,1.625 29.578,-38.679l0,-3.38l-34.519,0l0,4.68l29.383,0l-0.78,-1.625 -29.513,38.614z"
|
||||
android:strokeWidth="0.5"
|
||||
android:fillColor="#ffffff" />
|
||||
android:pathData="m34.113,42.944l25.806,0l0,-3.33l-22.198,0l0.555,1.156 21.042,-27.517L59.318,10.849L34.761,10.849L34.761,14.178l20.903,0l-0.555,-1.156 -20.996,27.47z"
|
||||
android:strokeWidth="1.196"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources />
|
||||
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<input-overlay-default-configs>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_PLUS</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>272</padding-horizontal>
|
||||
<padding-vertical>24</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_MINUS</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<padding-horizontal>272</padding-horizontal>
|
||||
<padding-vertical>24</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_HOME</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>208</padding-horizontal>
|
||||
<padding-vertical>80</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_L</name>
|
||||
<padding-horizontal>48</padding-horizontal>
|
||||
<padding-vertical>8</padding-vertical>
|
||||
<width>72</width>
|
||||
<height>36</height>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_ZL</name>
|
||||
<padding-horizontal>48</padding-horizontal>
|
||||
<padding-vertical>64</padding-vertical>
|
||||
<width>72</width>
|
||||
<height>36</height>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_R</name>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>48</padding-horizontal>
|
||||
<padding-vertical>8</padding-vertical>
|
||||
<width>72</width>
|
||||
<height>36</height>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_ZR</name>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>48</padding-horizontal>
|
||||
<padding-vertical>64</padding-vertical>
|
||||
<width>72</width>
|
||||
<height>36</height>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_C</name>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>60</padding-horizontal>
|
||||
<padding-vertical>8</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_Z</name>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>48</padding-horizontal>
|
||||
<padding-vertical>64</padding-vertical>
|
||||
<width>72</width>
|
||||
<height>36</height>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_A</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>32</padding-horizontal>
|
||||
<padding-vertical>56</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_Y</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>128</padding-horizontal>
|
||||
<padding-vertical>56</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_X</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>80</padding-horizontal>
|
||||
<padding-vertical>104</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_B</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>80</padding-horizontal>
|
||||
<padding-vertical>8</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_ONE</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>128</padding-horizontal>
|
||||
<padding-vertical>56</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_TWO</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>80</padding-horizontal>
|
||||
<padding-vertical>104</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_R_STICK_CLICK</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<align-end>true</align-end>
|
||||
<padding-horizontal>208</padding-horizontal>
|
||||
<padding-vertical>80</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>BUTTON_L_STICK_CLICK</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<padding-horizontal>208</padding-horizontal>
|
||||
<padding-vertical>80</padding-vertical>
|
||||
<size>48</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>AXIS_LEFT</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<padding-horizontal>144</padding-horizontal>
|
||||
<padding-vertical>120</padding-vertical>
|
||||
<size>72</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>AXIS_RIGHT</name>
|
||||
<align-end>true</align-end>
|
||||
<align-bottom>true</align-bottom>
|
||||
<padding-horizontal>144</padding-horizontal>
|
||||
<padding-vertical>120</padding-vertical>
|
||||
<size>72</size>
|
||||
</input-overlay-config>
|
||||
<input-overlay-config>
|
||||
<name>DPAD</name>
|
||||
<align-bottom>true</align-bottom>
|
||||
<size>144</size>
|
||||
<padding-horizontal>8</padding-horizontal>
|
||||
<padding-vertical>8</padding-vertical>
|
||||
</input-overlay-config>
|
||||
</input-overlay-default-configs>
|
||||
Reference in New Issue
Block a user