From b0a434b99fbfa9052ad44b15a70c4fef99aff1e3 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Fri, 24 Mar 2023 03:18:19 -0400 Subject: [PATCH] android: Re-implement overlay editing --- .../yuzu_emu/fragments/EmulationFragment.kt | 1 - .../org/yuzu/yuzu_emu/overlay/InputOverlay.kt | 113 +++++++++++++++++- .../overlay/InputOverlayDrawableButton.kt | 48 ++++++-- .../overlay/InputOverlayDrawableDpad.kt | 36 +++++- .../overlay/InputOverlayDrawableJoystick.kt | 72 +++++++++-- 5 files changed, 245 insertions(+), 25 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 4ba283ddd..18517bdf8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -90,7 +90,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } // Setup overlay. - resetInputOverlay() updateShowFpsOverlay() binding.inGameMenu.getHeaderView(0).findViewById(R.id.text_game_title).text = diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index acd4a1fe2..97e0ba3df 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt @@ -45,9 +45,15 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context private val overlayButtons: MutableSet = HashSet() private val overlayDpads: MutableSet = HashSet() private val overlayJoysticks: MutableSet = HashSet() + private var inEditMode = false + private var buttonBeingConfigured: InputOverlayDrawableButton? = null + private var dpadBeingConfigured: InputOverlayDrawableDpad? = null + private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null + private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) + private val gyro = FloatArray(3) private val accel = FloatArray(3) private var motionTimestamp: Long = 0 @@ -114,7 +120,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context } NativeLibrary.onGamePadButtonEvent( NativeLibrary.Player1Device, - button.id, + button.buttonId, button.status ) shouldUpdateView = true @@ -224,8 +230,109 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context return false } - private fun onTouchWhileEditing(event: MotionEvent?): Boolean { - // TODO: Reimplement this + private fun onTouchWhileEditing(event: MotionEvent): Boolean { + 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 } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt index bf0c2f3e6..99d7d9521 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt @@ -24,32 +24,30 @@ class InputOverlayDrawableButton( res: Resources, defaultStateBitmap: 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 var trackId: Int // The drawable position on the screen private var buttonPositionX = 0 private var buttonPositionY = 0 + val width: Int val height: Int + private val defaultStateBitmap: BitmapDrawable private val pressedStateBitmap: BitmapDrawable private var pressedState = false + private var previousTouchX = 0 + private var previousTouchY = 0 + var controlPositionX = 0 + var controlPositionY = 0 + init { this.defaultStateBitmap = BitmapDrawable(res, defaultStateBitmap) this.pressedStateBitmap = BitmapDrawable(res, pressedStateBitmap) - id = buttonId trackId = -1 width = this.defaultStateBitmap.intrinsicWidth height = this.defaultStateBitmap.intrinsicHeight @@ -104,6 +102,34 @@ class InputOverlayDrawableButton( private val currentStateBitmapDrawable: BitmapDrawable 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) { defaultStateBitmap.setBounds(left, top, right, bottom) pressedStateBitmap.setBounds(left, top, right, bottom) @@ -111,6 +137,6 @@ class InputOverlayDrawableButton( val status: Int get() = if (pressedState) ButtonState.PRESSED else ButtonState.RELEASED - private val bounds: Rect + val bounds: Rect get() = defaultStateBitmap.bounds } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt index 34d18eb1d..625cad661 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt @@ -45,13 +45,19 @@ class InputOverlayDrawableDpad( val leftId: Int val rightId: Int var trackId: Int - private var controlPositionX = 0 - private var controlPositionY = 0 + val width: Int val height: Int + private val defaultStateBitmap: BitmapDrawable private val pressedOneDirectionStateBitmap: 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 downButtonState = false private var leftButtonState = false @@ -215,6 +221,32 @@ class InputOverlayDrawableDpad( val rightStatus: Int 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) { controlPositionX = x controlPositionY = y diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt index 11ab0b829..1960eaff0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt @@ -39,31 +39,39 @@ class InputOverlayDrawableJoystick( val joystickId: Int, val buttonId: Int ) { - // The ID value what motion event is tracking var trackId = -1 + var xAxis = 0f private var yAxis = 0f - private var controlPositionX = 0 - private var controlPositionY = 0 + val width: Int val height: Int + private var virtBounds: Rect - private val origBounds: Rect + private var origBounds: Rect + private val outerBitmap: BitmapDrawable private val defaultStateInnerBitmap: BitmapDrawable private val pressedStateInnerBitmap: BitmapDrawable + + private var previousTouchX = 0 + private var previousTouchY = 0 + var controlPositionX = 0 + var controlPositionY = 0 + private val boundsBoxBitmap: BitmapDrawable + private var pressedState = false // TODO: Add button support val buttonStatus: Int get() = NativeLibrary.ButtonState.RELEASED - var bounds: Rect? + var bounds: Rect get() = outerBitmap.bounds set(bounds) { - outerBitmap.bounds = bounds!! + outerBitmap.bounds = bounds } // Nintendo joysticks have y axis inverted @@ -83,7 +91,7 @@ class InputOverlayDrawableJoystick( bounds = rectOuter defaultStateInnerBitmap.bounds = rectInner pressedStateInnerBitmap.bounds = rectInner - virtBounds = bounds!! + virtBounds = bounds origBounds = outerBitmap.copyBounds() boundsBoxBitmap.alpha = 0 boundsBoxBitmap.bounds = virtBounds @@ -106,8 +114,9 @@ class InputOverlayDrawableJoystick( motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN val isActionUp = motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP + if (isActionDown) { - if (!bounds!!.contains(xPosition, yPosition)) { + if (!bounds.contains(xPosition, yPosition)) { return false } pressedState = true @@ -122,6 +131,7 @@ class InputOverlayDrawableJoystick( boundsBoxBitmap.bounds = virtBounds trackId = pointerId } + if (isActionUp) { if (trackId != pointerId) { return false @@ -147,7 +157,9 @@ class InputOverlayDrawableJoystick( trackId = -1 return true } + if (trackId == -1) return false + for (i in 0 until event.pointerCount) { if (trackId != event.getPointerId(i)) { continue @@ -179,6 +191,50 @@ class InputOverlayDrawableJoystick( 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() { var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()