android: Re-implement overlay editing

This commit is contained in:
Charles Lombardo 2023-03-24 03:18:19 -04:00 committed by bunnei
parent 5807cf1b4d
commit b0a434b99f
5 changed files with 245 additions and 25 deletions

View File

@ -90,7 +90,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
// Setup overlay. // Setup overlay.
resetInputOverlay()
updateShowFpsOverlay() updateShowFpsOverlay()
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =

View File

@ -45,9 +45,15 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet() private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet()
private var inEditMode = false private var inEditMode = false
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
private val preferences: SharedPreferences = private val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
private val gyro = FloatArray(3) private val gyro = FloatArray(3)
private val accel = FloatArray(3) private val accel = FloatArray(3)
private var motionTimestamp: Long = 0 private var motionTimestamp: Long = 0
@ -114,7 +120,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
} }
NativeLibrary.onGamePadButtonEvent( NativeLibrary.onGamePadButtonEvent(
NativeLibrary.Player1Device, NativeLibrary.Player1Device,
button.id, button.buttonId,
button.status button.status
) )
shouldUpdateView = true shouldUpdateView = true
@ -224,8 +230,109 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
return false return false
} }
private fun onTouchWhileEditing(event: MotionEvent?): Boolean { private fun onTouchWhileEditing(event: MotionEvent): Boolean {
// TODO: Reimplement this val pointerIndex = event.actionIndex
val fingerPositionX = event.getX(pointerIndex).toInt()
val fingerPositionY = event.getY(pointerIndex).toInt()
// TODO: Provide support for portrait layout
//val orientation =
// if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
for (button in overlayButtons) {
// Determine the button state to apply based on the MotionEvent action flag.
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN ->
// If no button is being moved now, remember the currently touched button to move.
if (buttonBeingConfigured == null &&
button.bounds.contains(
fingerPositionX,
fingerPositionY
)
) {
buttonBeingConfigured = button
buttonBeingConfigured!!.onConfigureTouch(event)
}
MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) {
buttonBeingConfigured!!.onConfigureTouch(event)
invalidate()
return true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
// Persist button position by saving new place.
saveControlPosition(
buttonBeingConfigured!!.buttonId,
buttonBeingConfigured!!.bounds.centerX(),
buttonBeingConfigured!!.bounds.centerY(),
""
)
buttonBeingConfigured = null
}
}
}
for (dpad in overlayDpads) {
// Determine the button state to apply based on the MotionEvent action flag.
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN ->
// If no button is being moved now, remember the currently touched button to move.
if (buttonBeingConfigured == null &&
dpad.bounds.contains(fingerPositionX, fingerPositionY)
) {
dpadBeingConfigured = dpad
dpadBeingConfigured!!.onConfigureTouch(event)
}
MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) {
dpadBeingConfigured!!.onConfigureTouch(event)
invalidate()
return true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
// Persist button position by saving new place.
saveControlPosition(
dpadBeingConfigured!!.upId,
dpadBeingConfigured!!.bounds.centerX(),
dpadBeingConfigured!!.bounds.centerY(),
""
)
dpadBeingConfigured = null
}
}
}
for (joystick in overlayJoysticks) {
when (event.action) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
joystick.bounds.contains(
fingerPositionX,
fingerPositionY
)
) {
joystickBeingConfigured = joystick
joystickBeingConfigured!!.onConfigureTouch(event)
}
MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) {
joystickBeingConfigured!!.onConfigureTouch(event)
invalidate()
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
saveControlPosition(
joystickBeingConfigured!!.buttonId,
joystickBeingConfigured!!.bounds.centerX(),
joystickBeingConfigured!!.bounds.centerY(),
""
)
joystickBeingConfigured = null
}
}
}
return true return true
} }

View File

@ -24,32 +24,30 @@ class InputOverlayDrawableButton(
res: Resources, res: Resources,
defaultStateBitmap: Bitmap, defaultStateBitmap: Bitmap,
pressedStateBitmap: Bitmap, pressedStateBitmap: Bitmap,
buttonId: Int val buttonId: Int
) { ) {
/**
* Gets this InputOverlayDrawableButton's button ID.
*
* @return this InputOverlayDrawableButton's button ID.
*/
// The ID value what type of button this Drawable represents.
val id: Int
// The ID value what motion event is tracking // The ID value what motion event is tracking
var trackId: Int var trackId: Int
// The drawable position on the screen // The drawable position on the screen
private var buttonPositionX = 0 private var buttonPositionX = 0
private var buttonPositionY = 0 private var buttonPositionY = 0
val width: Int val width: Int
val height: Int val height: Int
private val defaultStateBitmap: BitmapDrawable private val defaultStateBitmap: BitmapDrawable
private val pressedStateBitmap: BitmapDrawable private val pressedStateBitmap: BitmapDrawable
private var pressedState = false private var pressedState = false
private var previousTouchX = 0
private var previousTouchY = 0
var controlPositionX = 0
var controlPositionY = 0
init { init {
this.defaultStateBitmap = BitmapDrawable(res, defaultStateBitmap) this.defaultStateBitmap = BitmapDrawable(res, defaultStateBitmap)
this.pressedStateBitmap = BitmapDrawable(res, pressedStateBitmap) this.pressedStateBitmap = BitmapDrawable(res, pressedStateBitmap)
id = buttonId
trackId = -1 trackId = -1
width = this.defaultStateBitmap.intrinsicWidth width = this.defaultStateBitmap.intrinsicWidth
height = this.defaultStateBitmap.intrinsicHeight height = this.defaultStateBitmap.intrinsicHeight
@ -104,6 +102,34 @@ class InputOverlayDrawableButton(
private val currentStateBitmapDrawable: BitmapDrawable private val currentStateBitmapDrawable: BitmapDrawable
get() = if (pressedState) pressedStateBitmap else defaultStateBitmap get() = if (pressedState) pressedStateBitmap else defaultStateBitmap
fun onConfigureTouch(event: MotionEvent): Boolean {
val pointerIndex = event.actionIndex
val fingerPositionX = event.getX(pointerIndex).toInt()
val fingerPositionY = event.getY(pointerIndex).toInt()
when (event.action) {
MotionEvent.ACTION_DOWN -> {
previousTouchX = fingerPositionX
previousTouchY = fingerPositionY
controlPositionX = fingerPositionX - (width / 2)
controlPositionY = fingerPositionY - (height / 2)
}
MotionEvent.ACTION_MOVE -> {
controlPositionX += fingerPositionX - previousTouchX
controlPositionY += fingerPositionY - previousTouchY
setBounds(
controlPositionX,
controlPositionY,
width + controlPositionX,
height + controlPositionY
)
previousTouchX = fingerPositionX
previousTouchY = fingerPositionY
}
}
return true
}
fun setBounds(left: Int, top: Int, right: Int, bottom: Int) { fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
defaultStateBitmap.setBounds(left, top, right, bottom) defaultStateBitmap.setBounds(left, top, right, bottom)
pressedStateBitmap.setBounds(left, top, right, bottom) pressedStateBitmap.setBounds(left, top, right, bottom)
@ -111,6 +137,6 @@ class InputOverlayDrawableButton(
val status: Int val status: Int
get() = if (pressedState) ButtonState.PRESSED else ButtonState.RELEASED get() = if (pressedState) ButtonState.PRESSED else ButtonState.RELEASED
private val bounds: Rect val bounds: Rect
get() = defaultStateBitmap.bounds get() = defaultStateBitmap.bounds
} }

View File

@ -45,13 +45,19 @@ class InputOverlayDrawableDpad(
val leftId: Int val leftId: Int
val rightId: Int val rightId: Int
var trackId: Int var trackId: Int
private var controlPositionX = 0
private var controlPositionY = 0
val width: Int val width: Int
val height: Int val height: Int
private val defaultStateBitmap: BitmapDrawable private val defaultStateBitmap: BitmapDrawable
private val pressedOneDirectionStateBitmap: BitmapDrawable private val pressedOneDirectionStateBitmap: BitmapDrawable
private val pressedTwoDirectionsStateBitmap: BitmapDrawable private val pressedTwoDirectionsStateBitmap: BitmapDrawable
private var previousTouchX = 0
private var previousTouchY = 0
private var controlPositionX = 0
private var controlPositionY = 0
private var upButtonState = false private var upButtonState = false
private var downButtonState = false private var downButtonState = false
private var leftButtonState = false private var leftButtonState = false
@ -215,6 +221,32 @@ class InputOverlayDrawableDpad(
val rightStatus: Int val rightStatus: Int
get() = if (rightButtonState) ButtonState.PRESSED else ButtonState.RELEASED get() = if (rightButtonState) ButtonState.PRESSED else ButtonState.RELEASED
fun onConfigureTouch(event: MotionEvent): Boolean {
val pointerIndex = event.actionIndex
val fingerPositionX = event.getX(pointerIndex).toInt()
val fingerPositionY = event.getY(pointerIndex).toInt()
when (event.action) {
MotionEvent.ACTION_DOWN -> {
previousTouchX = fingerPositionX
previousTouchY = fingerPositionY
}
MotionEvent.ACTION_MOVE -> {
controlPositionX += fingerPositionX - previousTouchX
controlPositionY += fingerPositionY - previousTouchY
setBounds(
controlPositionX,
controlPositionY,
width + controlPositionX,
height + controlPositionY
)
previousTouchX = fingerPositionX
previousTouchY = fingerPositionY
}
}
return true
}
fun setPosition(x: Int, y: Int) { fun setPosition(x: Int, y: Int) {
controlPositionX = x controlPositionX = x
controlPositionY = y controlPositionY = y

View File

@ -39,31 +39,39 @@ class InputOverlayDrawableJoystick(
val joystickId: Int, val joystickId: Int,
val buttonId: Int val buttonId: Int
) { ) {
// The ID value what motion event is tracking // The ID value what motion event is tracking
var trackId = -1 var trackId = -1
var xAxis = 0f var xAxis = 0f
private var yAxis = 0f private var yAxis = 0f
private var controlPositionX = 0
private var controlPositionY = 0
val width: Int val width: Int
val height: Int val height: Int
private var virtBounds: Rect private var virtBounds: Rect
private val origBounds: Rect private var origBounds: Rect
private val outerBitmap: BitmapDrawable private val outerBitmap: BitmapDrawable
private val defaultStateInnerBitmap: BitmapDrawable private val defaultStateInnerBitmap: BitmapDrawable
private val pressedStateInnerBitmap: BitmapDrawable private val pressedStateInnerBitmap: BitmapDrawable
private var previousTouchX = 0
private var previousTouchY = 0
var controlPositionX = 0
var controlPositionY = 0
private val boundsBoxBitmap: BitmapDrawable private val boundsBoxBitmap: BitmapDrawable
private var pressedState = false private var pressedState = false
// TODO: Add button support // TODO: Add button support
val buttonStatus: Int val buttonStatus: Int
get() = get() =
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
var bounds: Rect? var bounds: Rect
get() = outerBitmap.bounds get() = outerBitmap.bounds
set(bounds) { set(bounds) {
outerBitmap.bounds = bounds!! outerBitmap.bounds = bounds
} }
// Nintendo joysticks have y axis inverted // Nintendo joysticks have y axis inverted
@ -83,7 +91,7 @@ class InputOverlayDrawableJoystick(
bounds = rectOuter bounds = rectOuter
defaultStateInnerBitmap.bounds = rectInner defaultStateInnerBitmap.bounds = rectInner
pressedStateInnerBitmap.bounds = rectInner pressedStateInnerBitmap.bounds = rectInner
virtBounds = bounds!! virtBounds = bounds
origBounds = outerBitmap.copyBounds() origBounds = outerBitmap.copyBounds()
boundsBoxBitmap.alpha = 0 boundsBoxBitmap.alpha = 0
boundsBoxBitmap.bounds = virtBounds boundsBoxBitmap.bounds = virtBounds
@ -106,8 +114,9 @@ class InputOverlayDrawableJoystick(
motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
val isActionUp = val isActionUp =
motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
if (isActionDown) { if (isActionDown) {
if (!bounds!!.contains(xPosition, yPosition)) { if (!bounds.contains(xPosition, yPosition)) {
return false return false
} }
pressedState = true pressedState = true
@ -122,6 +131,7 @@ class InputOverlayDrawableJoystick(
boundsBoxBitmap.bounds = virtBounds boundsBoxBitmap.bounds = virtBounds
trackId = pointerId trackId = pointerId
} }
if (isActionUp) { if (isActionUp) {
if (trackId != pointerId) { if (trackId != pointerId) {
return false return false
@ -147,7 +157,9 @@ class InputOverlayDrawableJoystick(
trackId = -1 trackId = -1
return true return true
} }
if (trackId == -1) return false if (trackId == -1) return false
for (i in 0 until event.pointerCount) { for (i in 0 until event.pointerCount) {
if (trackId != event.getPointerId(i)) { if (trackId != event.getPointerId(i)) {
continue continue
@ -179,6 +191,50 @@ class InputOverlayDrawableJoystick(
return false return false
} }
fun onConfigureTouch(event: MotionEvent): Boolean {
val pointerIndex = event.actionIndex
val fingerPositionX = event.getX(pointerIndex).toInt()
val fingerPositionY = event.getY(pointerIndex).toInt()
when (event.action) {
MotionEvent.ACTION_DOWN -> {
previousTouchX = fingerPositionX
previousTouchY = fingerPositionY
controlPositionX = fingerPositionX - (width / 2)
controlPositionY = fingerPositionY - (height / 2)
}
MotionEvent.ACTION_MOVE -> {
controlPositionX += fingerPositionX - previousTouchX
controlPositionY += fingerPositionY - previousTouchY
bounds = Rect(
controlPositionX,
controlPositionY,
outerBitmap.intrinsicWidth + controlPositionX,
outerBitmap.intrinsicHeight + controlPositionY
)
virtBounds = Rect(
controlPositionX,
controlPositionY,
outerBitmap.intrinsicWidth + controlPositionX,
outerBitmap.intrinsicHeight + controlPositionY
)
setInnerBounds()
bounds = Rect(
Rect(
controlPositionX,
controlPositionY,
outerBitmap.intrinsicWidth + controlPositionX,
outerBitmap.intrinsicHeight + controlPositionY
)
)
previousTouchX = fingerPositionX
previousTouchY = fingerPositionY
}
}
origBounds = outerBitmap.copyBounds()
return true
}
private fun setInnerBounds() { private fun setInnerBounds() {
var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()