From ddb0de04104ca132762ba7a6644d5c9145b47bf6 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 3 Jun 2023 19:06:33 -0400 Subject: [PATCH] Android: Convert InputOverlayDrawableJoystick to Kotlin --- .../overlay/InputOverlayDrawableJoystick.java | 318 ------------------ .../overlay/InputOverlayDrawableJoystick.kt | 258 ++++++++++++++ 2 files changed, 258 insertions(+), 318 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java deleted file mode 100644 index 6ebe731341..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright 2013 Dolphin Emulator Project - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -package org.dolphinemu.dolphinemu.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.dolphinemu.dolphinemu.features.input.model.InputOverrider; -import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; - -/** - * Custom {@link BitmapDrawable} that is capable - * of storing it's own ID. - */ -public final class InputOverlayDrawableJoystick -{ - private float mCurrentX = 0.0f; - private float mCurrentY = 0.0f; - private int trackId = -1; - private final int mJoystickLegacyId; - private final int mJoystickXControl; - private final int mJoystickYControl; - private int mControlPositionX, mControlPositionY; - private int mPreviousTouchX, mPreviousTouchY; - private final int mWidth; - private final int mHeight; - private final int mControllerIndex; - private Rect mVirtBounds; - private Rect mOrigBounds; - private int mOpacity; - private final BitmapDrawable mOuterBitmap; - private final BitmapDrawable mDefaultStateInnerBitmap; - private final BitmapDrawable mPressedStateInnerBitmap; - private final 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 legacyId Legacy identifier (ButtonType) for which joystick this is. - * @param xControl The control which the x value of the joystick will be written to. - * @param yControl The control which the y value of the joystick will be written to. - */ - public InputOverlayDrawableJoystick(Resources res, Bitmap bitmapOuter, Bitmap bitmapInnerDefault, - Bitmap bitmapInnerPressed, Rect rectOuter, Rect rectInner, int legacyId, int xControl, - int yControl, int controllerIndex) - { - mJoystickLegacyId = legacyId; - mJoystickXControl = xControl; - mJoystickYControl = yControl; - - 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(); - - if (controllerIndex < 0 || controllerIndex >= 4) - throw new IllegalArgumentException("controllerIndex must be 0-3"); - mControllerIndex = controllerIndex; - - setBounds(rectOuter); - mDefaultStateInnerBitmap.setBounds(rectInner); - mPressedStateInnerBitmap.setBounds(rectInner); - mVirtBounds = getBounds(); - mOrigBounds = mOuterBitmap.copyBounds(); - mBoundsBoxBitmap.setAlpha(0); - mBoundsBoxBitmap.setBounds(getVirtBounds()); - SetInnerBounds(); - } - - /** - * Gets this InputOverlayDrawableJoystick's legacy ID. - * - * @return this InputOverlayDrawableJoystick's legacy ID. - */ - public int getLegacyId() - { - return mJoystickLegacyId; - } - - public void draw(Canvas canvas) - { - mOuterBitmap.draw(canvas); - getCurrentStateBitmapDrawable().draw(canvas); - mBoundsBoxBitmap.draw(canvas); - } - - public boolean TrackEvent(MotionEvent event) - { - boolean reCenter = BooleanSetting.MAIN_JOYSTICK_REL_CENTER.getBoolean(); - int action = event.getActionMasked(); - boolean firstPointer = action != MotionEvent.ACTION_POINTER_DOWN && - action != MotionEvent.ACTION_POINTER_UP; - int pointerIndex = firstPointer ? 0 : event.getActionIndex(); - boolean pressed = false; - - switch (action) - { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - if (getBounds().contains((int) event.getX(pointerIndex), (int) event.getY(pointerIndex))) - { - mPressedState = pressed = true; - mOuterBitmap.setAlpha(0); - mBoundsBoxBitmap.setAlpha(mOpacity); - if (reCenter) - { - getVirtBounds().offset((int) event.getX(pointerIndex) - getVirtBounds().centerX(), - (int) event.getY(pointerIndex) - getVirtBounds().centerY()); - } - mBoundsBoxBitmap.setBounds(getVirtBounds()); - trackId = event.getPointerId(pointerIndex); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - if (trackId == event.getPointerId(pointerIndex)) - { - pressed = true; - mPressedState = false; - mCurrentX = mCurrentY = 0.0f; - mOuterBitmap.setAlpha(mOpacity); - 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(); - trackId = -1; - } - break; - } - - if (trackId == -1) - return pressed; - - for (int i = 0; i < event.getPointerCount(); i++) - { - if (trackId == event.getPointerId(i)) - { - 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(); - mCurrentX = touchX / maxX; - mCurrentY = touchY / maxY; - - SetInnerBounds(); - } - } - return pressed; - } - - public void onConfigureTouch(MotionEvent event) - { - switch (event.getAction()) - { - case MotionEvent.ACTION_DOWN: - mPreviousTouchX = (int) event.getX(); - mPreviousTouchY = (int) event.getY(); - break; - case MotionEvent.ACTION_MOVE: - int deltaX = (int) event.getX() - mPreviousTouchX; - int deltaY = (int) event.getY() - mPreviousTouchY; - mControlPositionX += deltaX; - mControlPositionY += deltaY; - setBounds(new Rect(mControlPositionX, mControlPositionY, - mOuterBitmap.getIntrinsicWidth() + mControlPositionX, - mOuterBitmap.getIntrinsicHeight() + mControlPositionY)); - setVirtBounds(new Rect(mControlPositionX, mControlPositionY, - mOuterBitmap.getIntrinsicWidth() + mControlPositionX, - mOuterBitmap.getIntrinsicHeight() + mControlPositionY)); - SetInnerBounds(); - setOrigBounds(new Rect(new Rect(mControlPositionX, mControlPositionY, - mOuterBitmap.getIntrinsicWidth() + mControlPositionX, - mOuterBitmap.getIntrinsicHeight() + mControlPositionY))); - mPreviousTouchX = (int) event.getX(); - mPreviousTouchY = (int) event.getY(); - break; - } - } - - public float getX() - { - return mCurrentX; - } - - public float getY() - { - return mCurrentY; - } - - public int getXControl() - { - return mJoystickXControl; - } - - public int getYControl() - { - return mJoystickYControl; - } - - private void SetInnerBounds() - { - double x = mCurrentX; - double y = mCurrentY; - - double angle = Math.atan2(y, x) + Math.PI + Math.PI; - double radius = Math.hypot(y, x); - double maxRadius = InputOverrider.getGateRadiusAtAngle(mControllerIndex, mJoystickXControl, - angle); - if (radius > maxRadius) - { - y = maxRadius * Math.sin(angle); - x = maxRadius * Math.cos(angle); - mCurrentY = (float) y; - mCurrentX = (float) x; - } - - int pixelX = getVirtBounds().centerX() + (int) (x * (getVirtBounds().width() / 2)); - int pixelY = getVirtBounds().centerY() + (int) (y * (getVirtBounds().height() / 2)); - - int width = mPressedStateInnerBitmap.getBounds().width() / 2; - int height = mPressedStateInnerBitmap.getBounds().height() / 2; - mDefaultStateInnerBitmap.setBounds(pixelX - width, pixelY - height, pixelX + width, - pixelY + height); - mPressedStateInnerBitmap.setBounds(mDefaultStateInnerBitmap.getBounds()); - } - - public void setPosition(int x, int y) - { - mControlPositionX = x; - mControlPositionY = y; - } - - private BitmapDrawable getCurrentStateBitmapDrawable() - { - return mPressedState ? mPressedStateInnerBitmap : mDefaultStateInnerBitmap; - } - - public void setBounds(Rect bounds) - { - mOuterBitmap.setBounds(bounds); - } - - public void setOpacity(int value) - { - mOpacity = value; - - mDefaultStateInnerBitmap.setAlpha(value); - mPressedStateInnerBitmap.setAlpha(value); - - if (trackId == -1) - { - mOuterBitmap.setAlpha(value); - mBoundsBoxBitmap.setAlpha(0); - } - else - { - mOuterBitmap.setAlpha(0); - mBoundsBoxBitmap.setAlpha(value); - } - } - - public Rect getBounds() - { - return mOuterBitmap.getBounds(); - } - - private void setVirtBounds(Rect bounds) - { - mVirtBounds = bounds; - } - - private void setOrigBounds(Rect bounds) - { - mOrigBounds = bounds; - } - - private Rect getVirtBounds() - { - return mVirtBounds; - } - - public int getWidth() - { - return mWidth; - } - - public int getHeight() - { - return mHeight; - } - - public int getTrackId() - { - return trackId; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.kt new file mode 100644 index 0000000000..7dff339dcf --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.kt @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.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.dolphinemu.dolphinemu.features.input.model.InputOverrider +import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.hypot +import kotlin.math.sin + +/** + * 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 legacyId Legacy identifier (ButtonType) for which joystick this is. + * @param xControl The control which the x value of the joystick will be written to. + * @param yControl The control which the y value of the joystick will be written to. + */ +class InputOverlayDrawableJoystick( + res: Resources, + bitmapOuter: Bitmap, + bitmapInnerDefault: Bitmap, + bitmapInnerPressed: Bitmap, + rectOuter: Rect, + rectInner: Rect, + val legacyId: Int, + val xControl: Int, + val yControl: Int, + private val controllerIndex: Int +) { + var x = 0.0f + private set + var y = 0.0f + private set + var trackId = -1 + private set + private var controlPositionX = 0 + private var controlPositionY = 0 + private var previousTouchX = 0 + private var previousTouchY = 0 + val width: Int + val height: Int + private var virtBounds: Rect + private var origBounds: Rect + private var opacity = 0 + private val outerBitmap: BitmapDrawable + private val defaultStateInnerBitmap: BitmapDrawable + private val pressedStateInnerBitmap: BitmapDrawable + private val boundsBoxBitmap: BitmapDrawable + private var pressedState = false + + var bounds: Rect + get() = outerBitmap.bounds + set(bounds) { + outerBitmap.bounds = bounds + } + + init { + outerBitmap = BitmapDrawable(res, bitmapOuter) + defaultStateInnerBitmap = BitmapDrawable(res, bitmapInnerDefault) + pressedStateInnerBitmap = BitmapDrawable(res, bitmapInnerPressed) + boundsBoxBitmap = BitmapDrawable(res, bitmapOuter) + width = bitmapOuter.width + height = bitmapOuter.height + + require(!(controllerIndex < 0 || controllerIndex >= 4)) { "controllerIndex must be 0-3" } + + 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 trackEvent(event: MotionEvent): Boolean { + val reCenter = BooleanSetting.MAIN_JOYSTICK_REL_CENTER.boolean + val action = event.actionMasked + val firstPointer = action != MotionEvent.ACTION_POINTER_DOWN && + action != MotionEvent.ACTION_POINTER_UP + val pointerIndex = if (firstPointer) 0 else event.actionIndex + var pressed = false + + when (action) { + MotionEvent.ACTION_DOWN, + MotionEvent.ACTION_POINTER_DOWN -> { + if (bounds.contains( + event.getX(pointerIndex).toInt(), + event.getY(pointerIndex).toInt() + ) + ) { + pressed = true + pressedState = true + outerBitmap.alpha = 0 + boundsBoxBitmap.alpha = opacity + if (reCenter) { + virtBounds.offset( + event.getX(pointerIndex).toInt() - virtBounds.centerX(), + event.getY(pointerIndex).toInt() - virtBounds.centerY() + ) + } + boundsBoxBitmap.bounds = virtBounds + trackId = event.getPointerId(pointerIndex) + } + } + + MotionEvent.ACTION_UP, + MotionEvent.ACTION_POINTER_UP -> { + if (trackId == event.getPointerId(pointerIndex)) { + pressed = true + pressedState = false + y = 0f + x = y + outerBitmap.alpha = opacity + 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 + } + } + } + + if (trackId == -1) + return pressed + + for (i in 0 until event.pointerCount) { + if (trackId == event.getPointerId(i)) { + 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() + x = touchX / maxX + y = touchY / maxY + + setInnerBounds() + } + } + return pressed + } + + fun onConfigureTouch(event: MotionEvent) { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + previousTouchX = event.x.toInt() + previousTouchY = event.y.toInt() + } + + MotionEvent.ACTION_MOVE -> { + val deltaX = event.x.toInt() - previousTouchX + val deltaY = event.y.toInt() - previousTouchY + controlPositionX += deltaX + controlPositionY += deltaY + bounds = Rect( + controlPositionX, + controlPositionY, + outerBitmap.intrinsicWidth + controlPositionX, + outerBitmap.intrinsicHeight + controlPositionY + ) + virtBounds = Rect( + controlPositionX, + controlPositionY, + outerBitmap.intrinsicWidth + controlPositionX, + outerBitmap.intrinsicHeight + controlPositionY + ) + setInnerBounds() + origBounds = Rect( + Rect( + controlPositionX, + controlPositionY, + outerBitmap.intrinsicWidth + controlPositionX, + outerBitmap.intrinsicHeight + controlPositionY + ) + ) + previousTouchX = event.x.toInt() + previousTouchY = event.y.toInt() + } + } + } + + private fun setInnerBounds() { + var x = x.toDouble() + var y = y.toDouble() + + val angle = atan2(y, x) + Math.PI + Math.PI + val radius = hypot(y, x) + val maxRadius = InputOverrider.getGateRadiusAtAngle(controllerIndex, xControl, angle) + if (radius > maxRadius) { + x = maxRadius * cos(angle) + y = maxRadius * sin(angle) + this.x = x.toFloat() + this.y = y.toFloat() + } + + val pixelX = virtBounds.centerX() + (x * (virtBounds.width() / 2)).toInt() + val pixelY = virtBounds.centerY() + (y * (virtBounds.height() / 2)).toInt() + + val width = pressedStateInnerBitmap.bounds.width() / 2 + val height = pressedStateInnerBitmap.bounds.height() / 2 + defaultStateInnerBitmap.setBounds( + pixelX - width, + pixelY - height, + pixelX + width, + pixelY + height + ) + pressedStateInnerBitmap.bounds = defaultStateInnerBitmap.bounds + } + + fun setPosition(x: Int, y: Int) { + controlPositionX = x + controlPositionY = y + } + + private val currentStateBitmapDrawable: BitmapDrawable + get() = if (pressedState) pressedStateInnerBitmap else defaultStateInnerBitmap + + fun setOpacity(value: Int) { + opacity = value + + defaultStateInnerBitmap.alpha = value + pressedStateInnerBitmap.alpha = value + + if (trackId == -1) { + outerBitmap.alpha = value + boundsBoxBitmap.alpha = 0 + } else { + outerBitmap.alpha = 0 + boundsBoxBitmap.alpha = value + } + } +}