From 42b3e72e9608584d873b78defc63bc61218efd5b Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 11 Mar 2023 00:34:09 -0500 Subject: [PATCH] android: Convert InputOverlayDrawableJoystick to Kotlin --- .../overlay/InputOverlayDrawableJoystick.java | 243 ------------------ .../overlay/InputOverlayDrawableJoystick.kt | 205 +++++++++++++++ 2 files changed, 205 insertions(+), 243 deletions(-) delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.java create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.java deleted file mode 100644 index f7919e483e..0000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.java +++ /dev/null @@ -1,243 +0,0 @@ -/** - * Copyright 2013 Dolphin Emulator Project - * Licensed under GPLv2+ - * Refer to the license.txt file included. - */ - -package org.yuzu.yuzu_emu.overlay; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.view.MotionEvent; - -import org.yuzu.yuzu_emu.NativeLibrary; -import org.yuzu.yuzu_emu.NativeLibrary.ButtonType; -import org.yuzu.yuzu_emu.utils.EmulationMenuSettings; - -/** - * Custom {@link BitmapDrawable} that is capable - * of storing it's own ID. - */ -public final class InputOverlayDrawableJoystick { - // The ID value what type of joystick this Drawable represents. - private int mJoystickId; - // The ID value what type of button this Drawable represents. - private int mButtonId; - // The ID value what motion event is tracking - private int mTrackId = -1; - private float mXAxis; - private float mYAxis; - private int mControlPositionX, mControlPositionY; - private int mWidth; - private int mHeight; - private Rect mVirtBounds; - private Rect mOrigBounds; - private BitmapDrawable mOuterBitmap; - private BitmapDrawable mDefaultStateInnerBitmap; - private BitmapDrawable mPressedStateInnerBitmap; - private BitmapDrawable mBoundsBoxBitmap; - private boolean mPressedState = false; - - /** - * Constructor - * - * @param res {@link Resources} instance. - * @param bitmapOuter {@link Bitmap} which represents the outer non-movable part of the joystick. - * @param bitmapInnerDefault {@link Bitmap} which represents the default inner movable part of the joystick. - * @param bitmapInnerPressed {@link Bitmap} which represents the pressed inner movable part of the joystick. - * @param rectOuter {@link Rect} which represents the outer joystick bounds. - * @param rectInner {@link Rect} which represents the inner joystick bounds. - * @param joystick Identifier for which joystick this is. - */ - public InputOverlayDrawableJoystick(Resources res, Bitmap bitmapOuter, - Bitmap bitmapInnerDefault, Bitmap bitmapInnerPressed, - Rect rectOuter, Rect rectInner, int joystick, int button) { - mJoystickId = joystick; - mButtonId = button; - - mOuterBitmap = new BitmapDrawable(res, bitmapOuter); - mDefaultStateInnerBitmap = new BitmapDrawable(res, bitmapInnerDefault); - mPressedStateInnerBitmap = new BitmapDrawable(res, bitmapInnerPressed); - mBoundsBoxBitmap = new BitmapDrawable(res, bitmapOuter); - mWidth = bitmapOuter.getWidth(); - mHeight = bitmapOuter.getHeight(); - - setBounds(rectOuter); - mDefaultStateInnerBitmap.setBounds(rectInner); - mPressedStateInnerBitmap.setBounds(rectInner); - mVirtBounds = getBounds(); - mOrigBounds = mOuterBitmap.copyBounds(); - mBoundsBoxBitmap.setAlpha(0); - mBoundsBoxBitmap.setBounds(getVirtBounds()); - SetInnerBounds(); - } - - public void draw(Canvas canvas) { - mOuterBitmap.draw(canvas); - getCurrentStateBitmapDrawable().draw(canvas); - mBoundsBoxBitmap.draw(canvas); - } - - public boolean updateStatus(MotionEvent event) { - int pointerIndex = event.getActionIndex(); - int xPosition = (int) event.getX(pointerIndex); - int yPosition = (int) event.getY(pointerIndex); - int pointerId = event.getPointerId(pointerIndex); - int motion_event = event.getAction() & MotionEvent.ACTION_MASK; - boolean isActionDown = motion_event == MotionEvent.ACTION_DOWN || motion_event == MotionEvent.ACTION_POINTER_DOWN; - boolean isActionUp = motion_event == MotionEvent.ACTION_UP || motion_event == MotionEvent.ACTION_POINTER_UP; - - if (isActionDown) { - if (!getBounds().contains(xPosition, yPosition)) { - return false; - } - mPressedState = true; - mOuterBitmap.setAlpha(0); - mBoundsBoxBitmap.setAlpha(255); - if (EmulationMenuSettings.getJoystickRelCenter()) { - getVirtBounds().offset(xPosition - getVirtBounds().centerX(), - yPosition - getVirtBounds().centerY()); - } - mBoundsBoxBitmap.setBounds(getVirtBounds()); - mTrackId = pointerId; - } - - if (isActionUp) { - if (mTrackId != pointerId) { - return false; - } - mPressedState = false; - mXAxis = 0.0f; - mYAxis = 0.0f; - mOuterBitmap.setAlpha(255); - mBoundsBoxBitmap.setAlpha(0); - setVirtBounds(new Rect(mOrigBounds.left, mOrigBounds.top, mOrigBounds.right, - mOrigBounds.bottom)); - setBounds(new Rect(mOrigBounds.left, mOrigBounds.top, mOrigBounds.right, - mOrigBounds.bottom)); - SetInnerBounds(); - mTrackId = -1; - return true; - } - - if (mTrackId == -1) - return false; - - for (int i = 0; i < event.getPointerCount(); i++) { - if (mTrackId != event.getPointerId(i)) { - continue; - } - float touchX = event.getX(i); - float touchY = event.getY(i); - float maxY = getVirtBounds().bottom; - float maxX = getVirtBounds().right; - touchX -= getVirtBounds().centerX(); - maxX -= getVirtBounds().centerX(); - touchY -= getVirtBounds().centerY(); - maxY -= getVirtBounds().centerY(); - final float AxisX = touchX / maxX; - final float AxisY = touchY / maxY; - final float oldXAxis = mXAxis; - final float oldYAxis = mYAxis; - - // Clamp the circle pad input to a circle - final float angle = (float) Math.atan2(AxisY, AxisX); - float radius = (float) Math.sqrt(AxisX * AxisX + AxisY * AxisY); - if (radius > 1.0f) { - radius = 1.0f; - } - mXAxis = ((float) Math.cos(angle) * radius); - mYAxis = ((float) Math.sin(angle) * radius); - SetInnerBounds(); - return oldXAxis != mXAxis && oldYAxis != mYAxis; - } - - return false; - } - - private void SetInnerBounds() { - int X = getVirtBounds().centerX() + (int) ((mXAxis) * (getVirtBounds().width() / 2)); - int Y = getVirtBounds().centerY() + (int) ((mYAxis) * (getVirtBounds().height() / 2)); - - if (X > getVirtBounds().centerX() + (getVirtBounds().width() / 2)) - X = getVirtBounds().centerX() + (getVirtBounds().width() / 2); - if (X < getVirtBounds().centerX() - (getVirtBounds().width() / 2)) - X = getVirtBounds().centerX() - (getVirtBounds().width() / 2); - if (Y > getVirtBounds().centerY() + (getVirtBounds().height() / 2)) - Y = getVirtBounds().centerY() + (getVirtBounds().height() / 2); - if (Y < getVirtBounds().centerY() - (getVirtBounds().height() / 2)) - Y = getVirtBounds().centerY() - (getVirtBounds().height() / 2); - - int width = mPressedStateInnerBitmap.getBounds().width() / 2; - int height = mPressedStateInnerBitmap.getBounds().height() / 2; - mDefaultStateInnerBitmap.setBounds(X - width, Y - height, X + width, Y + height); - mPressedStateInnerBitmap.setBounds(mDefaultStateInnerBitmap.getBounds()); - } - - public void setPosition(int x, int y) { - mControlPositionX = x; - mControlPositionY = y; - } - - private BitmapDrawable getCurrentStateBitmapDrawable() { - return mPressedState ? mPressedStateInnerBitmap : mDefaultStateInnerBitmap; - } - - /** - * Gets this InputOverlayDrawableJoystick's button ID. - * - * @return this InputOverlayDrawableJoystick's button ID. - */ - public int getJoystickId() { - return mJoystickId; - } - - public float getXAxis() { - return mXAxis; - } - - public float getYAxis() { - // Nintendo joysticks have y axis inverted - return -mYAxis; - } - - public int getButtonId() { - return mButtonId; - } - - public int getTrackId() { - return mTrackId; - } - - public int getButtonStatus() { - // TODO: Add button support - return NativeLibrary.ButtonState.RELEASED; - } - - public Rect getBounds() { - return mOuterBitmap.getBounds(); - } - - public void setBounds(Rect bounds) { - mOuterBitmap.setBounds(bounds); - } - - private Rect getVirtBounds() { - return mVirtBounds; - } - - private void setVirtBounds(Rect bounds) { - mVirtBounds = bounds; - } - - public int getWidth() { - return mWidth; - } - - public int getHeight() { - return mHeight; - } -} 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 new file mode 100644 index 0000000000..84a3ea40b3 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt @@ -0,0 +1,205 @@ +package org.yuzu.yuzu_emu.overlay + +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.BitmapDrawable +import android.view.MotionEvent +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.utils.EmulationMenuSettings +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.sqrt + +/** + * Custom [BitmapDrawable] that is capable + * of storing it's own ID. + * + * @param res [Resources] instance. + * @param bitmapOuter [Bitmap] which represents the outer non-movable part of the joystick. + * @param bitmapInnerDefault [Bitmap] which represents the default inner movable part of the joystick. + * @param bitmapInnerPressed [Bitmap] which represents the pressed inner movable part of the joystick. + * @param rectOuter [Rect] which represents the outer joystick bounds. + * @param rectInner [Rect] which represents the inner joystick bounds. + * @param joystickId The ID value what type of joystick this Drawable represents. + * @param buttonId The ID value what type of button this Drawable represents. + */ +class InputOverlayDrawableJoystick( + res: Resources, + bitmapOuter: Bitmap, + bitmapInnerDefault: Bitmap, + bitmapInnerPressed: Bitmap, + rectOuter: Rect, + rectInner: Rect, + 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 val outerBitmap: BitmapDrawable + private val defaultStateInnerBitmap: BitmapDrawable + private val pressedStateInnerBitmap: BitmapDrawable + private val boundsBoxBitmap: BitmapDrawable + private var pressedState = false + + // TODO: Add button support + val buttonStatus: Int + get() = + NativeLibrary.ButtonState.RELEASED + var bounds: Rect? + get() = outerBitmap.bounds + set(bounds) { + outerBitmap.bounds = bounds!! + } + + // Nintendo joysticks have y axis inverted + val realYAxis: Float + get() = -yAxis + + private val currentStateBitmapDrawable: BitmapDrawable + get() = if (pressedState) pressedStateInnerBitmap else defaultStateInnerBitmap + + init { + outerBitmap = BitmapDrawable(res, bitmapOuter) + defaultStateInnerBitmap = BitmapDrawable(res, bitmapInnerDefault) + pressedStateInnerBitmap = BitmapDrawable(res, bitmapInnerPressed) + boundsBoxBitmap = BitmapDrawable(res, bitmapOuter) + width = bitmapOuter.width + height = bitmapOuter.height + bounds = rectOuter + defaultStateInnerBitmap.bounds = rectInner + pressedStateInnerBitmap.bounds = rectInner + virtBounds = bounds!! + origBounds = outerBitmap.copyBounds() + boundsBoxBitmap.alpha = 0 + boundsBoxBitmap.bounds = virtBounds + setInnerBounds() + } + + fun draw(canvas: Canvas?) { + outerBitmap.draw(canvas!!) + currentStateBitmapDrawable.draw(canvas) + boundsBoxBitmap.draw(canvas) + } + + fun updateStatus(event: MotionEvent): Boolean { + val pointerIndex = event.actionIndex + val xPosition = event.getX(pointerIndex).toInt() + val yPosition = event.getY(pointerIndex).toInt() + val pointerId = event.getPointerId(pointerIndex) + val motionEvent = event.action and MotionEvent.ACTION_MASK + val isActionDown = + 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)) { + return false + } + pressedState = true + outerBitmap.alpha = 0 + boundsBoxBitmap.alpha = 255 + if (EmulationMenuSettings.joystickRelCenter) { + virtBounds.offset( + xPosition - virtBounds.centerX(), + yPosition - virtBounds.centerY() + ) + } + boundsBoxBitmap.bounds = virtBounds + trackId = pointerId + } + if (isActionUp) { + if (trackId != pointerId) { + return false + } + pressedState = false + xAxis = 0.0f + yAxis = 0.0f + outerBitmap.alpha = 255 + boundsBoxBitmap.alpha = 0 + virtBounds = Rect( + origBounds.left, + origBounds.top, + origBounds.right, + origBounds.bottom + ) + bounds = Rect( + origBounds.left, + origBounds.top, + origBounds.right, + origBounds.bottom + ) + setInnerBounds() + trackId = -1 + return true + } + if (trackId == -1) return false + for (i in 0 until event.pointerCount) { + if (trackId != event.getPointerId(i)) { + continue + } + var touchX = event.getX(i) + var touchY = event.getY(i) + var maxY = virtBounds.bottom.toFloat() + var maxX = virtBounds.right.toFloat() + touchX -= virtBounds.centerX().toFloat() + maxX -= virtBounds.centerX().toFloat() + touchY -= virtBounds.centerY().toFloat() + maxY -= virtBounds.centerY().toFloat() + val axisX = touchX / maxX + val axisY = touchY / maxY + val oldXAxis = xAxis + val oldYAxis = yAxis + + // Clamp the circle pad input to a circle + val angle = atan2(axisY.toDouble(), axisX.toDouble()).toFloat() + var radius = sqrt((axisX * axisX + axisY * axisY).toDouble()).toFloat() + if (radius > 1.0f) { + radius = 1.0f + } + xAxis = cos(angle.toDouble()).toFloat() * radius + yAxis = sin(angle.toDouble()).toFloat() * radius + setInnerBounds() + return oldXAxis != xAxis && oldYAxis != yAxis + } + return false + } + + private fun setInnerBounds() { + var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() + var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() + if (x > virtBounds.centerX() + virtBounds.width() / 2) x = + virtBounds.centerX() + virtBounds.width() / 2 + if (x < virtBounds.centerX() - virtBounds.width() / 2) x = + virtBounds.centerX() - virtBounds.width() / 2 + if (y > virtBounds.centerY() + virtBounds.height() / 2) y = + virtBounds.centerY() + virtBounds.height() / 2 + if (y < virtBounds.centerY() - virtBounds.height() / 2) y = + virtBounds.centerY() - virtBounds.height() / 2 + val width = pressedStateInnerBitmap.bounds.width() / 2 + val height = pressedStateInnerBitmap.bounds.height() / 2 + defaultStateInnerBitmap.setBounds( + x - width, + y - height, + x + width, + y + height + ) + pressedStateInnerBitmap.bounds = defaultStateInnerBitmap.bounds + } + + fun setPosition(x: Int, y: Int) { + controlPositionX = x + controlPositionY = y + } +}