Refactored input overlay code

This commit is contained in:
SSimco
2025-03-10 17:15:53 +02:00
parent c587f73810
commit c25233404e
19 changed files with 965 additions and 1132 deletions
@@ -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))
}
}
@@ -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
}
@@ -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"
}
}
@@ -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)
}
}
}
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,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 -1
View File
@@ -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>