Merge pull request #11884 from t895/kotlin-overlay

Android: Convert InputOverlay to Kotlin
This commit is contained in:
JosJuice 2023-06-06 13:03:39 +02:00 committed by GitHub
commit 04fab7f2b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 3036 additions and 2723 deletions

View File

@ -238,7 +238,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
if (mInputOverlay != null)
{
mBinding.doneControlConfig.setVisibility(View.VISIBLE);
mInputOverlay.setIsInEditMode(true);
mInputOverlay.setEditMode(true);
}
}
@ -247,7 +247,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
if (mInputOverlay != null)
{
mBinding.doneControlConfig.setVisibility(View.GONE);
mInputOverlay.setIsInEditMode(false);
mInputOverlay.setEditMode(false);
}
}

View File

@ -1,144 +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;
/**
* Custom {@link BitmapDrawable} that is capable
* of storing it's own ID.
*/
public final class InputOverlayDrawableButton
{
// The legacy ID identifying what type of button this Drawable represents.
private int mLegacyId;
private int mControl;
private int mTrackId;
private int mPreviousTouchX, mPreviousTouchY;
private int mControlPositionX, mControlPositionY;
private int mWidth;
private int mHeight;
private BitmapDrawable mDefaultStateBitmap;
private BitmapDrawable mPressedStateBitmap;
private boolean mPressedState = false;
/**
* Constructor
*
* @param res {@link Resources} instance.
* @param defaultStateBitmap {@link Bitmap} to use with the default state Drawable.
* @param pressedStateBitmap {@link Bitmap} to use with the pressed state Drawable.
* @param legacyId Legacy identifier (ButtonType) for this type of button.
* @param control Control ID for this type of button.
*/
public InputOverlayDrawableButton(Resources res, Bitmap defaultStateBitmap,
Bitmap pressedStateBitmap, int legacyId, int control)
{
mTrackId = -1;
mDefaultStateBitmap = new BitmapDrawable(res, defaultStateBitmap);
mPressedStateBitmap = new BitmapDrawable(res, pressedStateBitmap);
mLegacyId = legacyId;
mControl = control;
mWidth = mDefaultStateBitmap.getIntrinsicWidth();
mHeight = mDefaultStateBitmap.getIntrinsicHeight();
}
/**
* Gets this InputOverlayDrawableButton's legacy button ID.
*/
public int getLegacyId()
{
return mLegacyId;
}
public int getControl()
{
return mControl;
}
public void setTrackId(int trackId)
{
mTrackId = trackId;
}
public int getTrackId()
{
return mTrackId;
}
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:
mControlPositionX += (int) event.getX() - mPreviousTouchX;
mControlPositionY += (int) event.getY() - mPreviousTouchY;
setBounds(mControlPositionX, mControlPositionY, getWidth() + mControlPositionX,
getHeight() + mControlPositionY);
mPreviousTouchX = (int) event.getX();
mPreviousTouchY = (int) event.getY();
break;
}
}
public void setPosition(int x, int y)
{
mControlPositionX = x;
mControlPositionY = y;
}
public void draw(Canvas canvas)
{
getCurrentStateBitmapDrawable().draw(canvas);
}
private BitmapDrawable getCurrentStateBitmapDrawable()
{
return mPressedState ? mPressedStateBitmap : mDefaultStateBitmap;
}
public void setBounds(int left, int top, int right, int bottom)
{
mDefaultStateBitmap.setBounds(left, top, right, bottom);
mPressedStateBitmap.setBounds(left, top, right, bottom);
}
public void setOpacity(int value)
{
mDefaultStateBitmap.setAlpha(value);
mPressedStateBitmap.setAlpha(value);
}
public Rect getBounds()
{
return mDefaultStateBitmap.getBounds();
}
public int getWidth()
{
return mWidth;
}
public int getHeight()
{
return mHeight;
}
public void setPressedState(boolean isPressed)
{
mPressedState = isPressed;
}
}

View File

@ -0,0 +1,97 @@
// 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
/**
* Custom [BitmapDrawable] that is capable
* of storing it's own ID.
*
* @param res [Resources] instance.
* @param defaultStateBitmap [Bitmap] to use with the default state Drawable.
* @param pressedStateBitmap [Bitmap] to use with the pressed state Drawable.
* @param legacyId Legacy identifier (ButtonType) for this type of button.
* @param control Control ID for this type of button.
*/
class InputOverlayDrawableButton(
res: Resources,
defaultStateBitmap: Bitmap,
pressedStateBitmap: Bitmap,
val legacyId: Int,
val control: Int
) {
var trackId: Int = -1
private var previousTouchX = 0
private var previousTouchY = 0
private var controlPositionX = 0
private var controlPositionY = 0
val width: Int
val height: Int
private val defaultStateBitmap: BitmapDrawable
private val pressedStateBitmap: BitmapDrawable
private var pressedState = false
init {
this.defaultStateBitmap = BitmapDrawable(res, defaultStateBitmap)
this.pressedStateBitmap = BitmapDrawable(res, pressedStateBitmap)
width = this.defaultStateBitmap.intrinsicWidth
height = this.defaultStateBitmap.intrinsicHeight
}
fun onConfigureTouch(event: MotionEvent) {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
previousTouchX = event.x.toInt()
previousTouchY = event.y.toInt()
}
MotionEvent.ACTION_MOVE -> {
controlPositionX += event.x.toInt() - previousTouchX
controlPositionY += event.y.toInt() - previousTouchY
setBounds(
controlPositionX,
controlPositionY,
width + controlPositionX,
height + controlPositionY
)
previousTouchX = event.x.toInt()
previousTouchY = event.y.toInt()
}
}
}
fun setPosition(x: Int, y: Int) {
controlPositionX = x
controlPositionY = y
}
fun draw(canvas: Canvas) {
currentStateBitmapDrawable.draw(canvas)
}
private val currentStateBitmapDrawable: BitmapDrawable
get() = if (pressedState) pressedStateBitmap else defaultStateBitmap
fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
defaultStateBitmap.setBounds(left, top, right, bottom)
pressedStateBitmap.setBounds(left, top, right, bottom)
}
fun setOpacity(value: Int) {
defaultStateBitmap.alpha = value
pressedStateBitmap.alpha = value
}
val bounds: Rect
get() = defaultStateBitmap.bounds
fun setPressedState(isPressed: Boolean) {
pressedState = isPressed
}
}

View File

@ -1,211 +0,0 @@
/*
* Copyright 2016 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;
/**
* Custom {@link BitmapDrawable} that is capable
* of storing it's own ID.
*/
public final class InputOverlayDrawableDpad
{
// The legacy ID identifying what type of button this Drawable represents.
private int mLegacyId;
private int[] mControls = new int[4];
private int mTrackId;
private int mPreviousTouchX, mPreviousTouchY;
private int mControlPositionX, mControlPositionY;
private int mWidth;
private int mHeight;
private BitmapDrawable mDefaultStateBitmap;
private BitmapDrawable mPressedOneDirectionStateBitmap;
private BitmapDrawable mPressedTwoDirectionsStateBitmap;
private int mPressState = STATE_DEFAULT;
public static final int STATE_DEFAULT = 0;
public static final int STATE_PRESSED_UP = 1;
public static final int STATE_PRESSED_DOWN = 2;
public static final int STATE_PRESSED_LEFT = 3;
public static final int STATE_PRESSED_RIGHT = 4;
public static final int STATE_PRESSED_UP_LEFT = 5;
public static final int STATE_PRESSED_UP_RIGHT = 6;
public static final int STATE_PRESSED_DOWN_LEFT = 7;
public static final int STATE_PRESSED_DOWN_RIGHT = 8;
/**
* Constructor
*
* @param res {@link Resources} instance.
* @param defaultStateBitmap {@link Bitmap} of the default state.
* @param pressedOneDirectionStateBitmap {@link Bitmap} of the pressed state in one direction.
* @param pressedTwoDirectionsStateBitmap {@link Bitmap} of the pressed state in two direction.
* @param legacyId Legacy identifier (ButtonType) for the up button.
* @param upControl Control identifier for the up button.
* @param downControl Control identifier for the down button.
* @param leftControl Control identifier for the left button.
* @param rightControl Control identifier for the right button.
*/
public InputOverlayDrawableDpad(Resources res, Bitmap defaultStateBitmap,
Bitmap pressedOneDirectionStateBitmap, Bitmap pressedTwoDirectionsStateBitmap,
int legacyId, int upControl, int downControl, int leftControl, int rightControl)
{
mTrackId = -1;
mDefaultStateBitmap = new BitmapDrawable(res, defaultStateBitmap);
mPressedOneDirectionStateBitmap = new BitmapDrawable(res, pressedOneDirectionStateBitmap);
mPressedTwoDirectionsStateBitmap = new BitmapDrawable(res, pressedTwoDirectionsStateBitmap);
mWidth = mDefaultStateBitmap.getIntrinsicWidth();
mHeight = mDefaultStateBitmap.getIntrinsicHeight();
mLegacyId = legacyId;
mControls[0] = upControl;
mControls[1] = downControl;
mControls[2] = leftControl;
mControls[3] = rightControl;
}
public void draw(Canvas canvas)
{
int px = mControlPositionX + (getWidth() / 2);
int py = mControlPositionY + (getHeight() / 2);
switch (mPressState)
{
case STATE_DEFAULT:
mDefaultStateBitmap.draw(canvas);
break;
case STATE_PRESSED_UP:
mPressedOneDirectionStateBitmap.draw(canvas);
break;
case STATE_PRESSED_RIGHT:
canvas.save();
canvas.rotate(90, px, py);
mPressedOneDirectionStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_DOWN:
canvas.save();
canvas.rotate(180, px, py);
mPressedOneDirectionStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_LEFT:
canvas.save();
canvas.rotate(270, px, py);
mPressedOneDirectionStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_UP_LEFT:
mPressedTwoDirectionsStateBitmap.draw(canvas);
break;
case STATE_PRESSED_UP_RIGHT:
canvas.save();
canvas.rotate(90, px, py);
mPressedTwoDirectionsStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_DOWN_RIGHT:
canvas.save();
canvas.rotate(180, px, py);
mPressedTwoDirectionsStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_DOWN_LEFT:
canvas.save();
canvas.rotate(270, px, py);
mPressedTwoDirectionsStateBitmap.draw(canvas);
canvas.restore();
break;
}
}
public int getLegacyId()
{
return mLegacyId;
}
/**
* Gets one of the InputOverlayDrawableDpad's control IDs.
*/
public int getControl(int direction)
{
return mControls[direction];
}
public void setTrackId(int trackId)
{
mTrackId = trackId;
}
public int getTrackId()
{
return mTrackId;
}
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:
mControlPositionX += (int) event.getX() - mPreviousTouchX;
mControlPositionY += (int) event.getY() - mPreviousTouchY;
setBounds(mControlPositionX, mControlPositionY, getWidth() + mControlPositionX,
getHeight() + mControlPositionY);
mPreviousTouchX = (int) event.getX();
mPreviousTouchY = (int) event.getY();
break;
}
}
public void setPosition(int x, int y)
{
mControlPositionX = x;
mControlPositionY = y;
}
public void setBounds(int left, int top, int right, int bottom)
{
mDefaultStateBitmap.setBounds(left, top, right, bottom);
mPressedOneDirectionStateBitmap.setBounds(left, top, right, bottom);
mPressedTwoDirectionsStateBitmap.setBounds(left, top, right, bottom);
}
public void setOpacity(int value)
{
mDefaultStateBitmap.setAlpha(value);
mPressedOneDirectionStateBitmap.setAlpha(value);
mPressedTwoDirectionsStateBitmap.setAlpha(value);
}
public Rect getBounds()
{
return mDefaultStateBitmap.getBounds();
}
public int getWidth()
{
return mWidth;
}
public int getHeight()
{
return mHeight;
}
public void setState(int pressState)
{
mPressState = pressState;
}
}

View File

@ -0,0 +1,189 @@
// 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
/**
* Custom [BitmapDrawable] that is capable
* of storing it's own ID.
*
* @param res [Resources] instance.
* @param defaultStateBitmap [Bitmap] of the default state.
* @param pressedOneDirectionStateBitmap [Bitmap] of the pressed state in one direction.
* @param pressedTwoDirectionsStateBitmap [Bitmap] of the pressed state in two direction.
* @param legacyId Legacy identifier (ButtonType) for the up button.
* @param upControl Control identifier for the up button.
* @param downControl Control identifier for the down button.
* @param leftControl Control identifier for the left button.
* @param rightControl Control identifier for the right button.
*/
class InputOverlayDrawableDpad(
res: Resources,
defaultStateBitmap: Bitmap,
pressedOneDirectionStateBitmap: Bitmap,
pressedTwoDirectionsStateBitmap: Bitmap,
val legacyId: Int,
upControl: Int,
downControl: Int,
leftControl: Int,
rightControl: Int
) {
private val controls = IntArray(4)
var trackId: Int = -1
private var previousTouchX = 0
private var previousTouchY = 0
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 pressState = STATE_DEFAULT
init {
this.defaultStateBitmap = BitmapDrawable(res, defaultStateBitmap)
this.pressedOneDirectionStateBitmap = BitmapDrawable(res, pressedOneDirectionStateBitmap)
this.pressedTwoDirectionsStateBitmap = BitmapDrawable(res, pressedTwoDirectionsStateBitmap)
width = this.defaultStateBitmap.intrinsicWidth
height = this.defaultStateBitmap.intrinsicHeight
controls[0] = upControl
controls[1] = downControl
controls[2] = leftControl
controls[3] = rightControl
}
fun draw(canvas: Canvas) {
val px = controlPositionX + width / 2
val py = controlPositionY + height / 2
when (pressState) {
STATE_DEFAULT -> defaultStateBitmap.draw(canvas)
STATE_PRESSED_UP -> pressedOneDirectionStateBitmap.draw(canvas)
STATE_PRESSED_RIGHT -> {
canvas.apply {
save()
rotate(90f, px.toFloat(), py.toFloat())
pressedOneDirectionStateBitmap.draw(canvas)
restore()
}
}
STATE_PRESSED_DOWN -> {
canvas.apply {
save()
rotate(180f, px.toFloat(), py.toFloat())
pressedOneDirectionStateBitmap.draw(canvas)
restore()
}
}
STATE_PRESSED_LEFT -> {
canvas.apply {
save()
rotate(270f, px.toFloat(), py.toFloat())
pressedOneDirectionStateBitmap.draw(canvas)
restore()
}
}
STATE_PRESSED_UP_LEFT -> pressedTwoDirectionsStateBitmap.draw(canvas)
STATE_PRESSED_UP_RIGHT -> {
canvas.apply {
save()
rotate(90f, px.toFloat(), py.toFloat())
pressedTwoDirectionsStateBitmap.draw(canvas)
restore()
}
}
STATE_PRESSED_DOWN_RIGHT -> {
canvas.apply {
save()
rotate(180f, px.toFloat(), py.toFloat())
pressedTwoDirectionsStateBitmap.draw(canvas)
restore()
}
}
STATE_PRESSED_DOWN_LEFT -> {
canvas.apply {
save()
rotate(270f, px.toFloat(), py.toFloat())
pressedTwoDirectionsStateBitmap.draw(canvas)
restore()
}
}
}
}
/**
* Gets one of the InputOverlayDrawableDpad's control IDs.
*/
fun getControl(direction: Int): Int {
return controls[direction]
}
fun onConfigureTouch(event: MotionEvent) {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
previousTouchX = event.x.toInt()
previousTouchY = event.y.toInt()
}
MotionEvent.ACTION_MOVE -> {
controlPositionX += event.x.toInt() - previousTouchX
controlPositionY += event.y.toInt() - previousTouchY
setBounds(
controlPositionX, controlPositionY, width + controlPositionX,
height + controlPositionY
)
previousTouchX = event.x.toInt()
previousTouchY = event.y.toInt()
}
}
}
fun setPosition(x: Int, y: Int) {
controlPositionX = x
controlPositionY = y
}
fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
defaultStateBitmap.setBounds(left, top, right, bottom)
pressedOneDirectionStateBitmap.setBounds(left, top, right, bottom)
pressedTwoDirectionsStateBitmap.setBounds(left, top, right, bottom)
}
fun setOpacity(value: Int) {
defaultStateBitmap.alpha = value
pressedOneDirectionStateBitmap.alpha = value
pressedTwoDirectionsStateBitmap.alpha = value
}
val bounds: Rect
get() = defaultStateBitmap.bounds
fun setState(pressState: Int) {
this.pressState = pressState
}
companion object {
const val STATE_DEFAULT = 0
const val STATE_PRESSED_UP = 1
const val STATE_PRESSED_DOWN = 2
const val STATE_PRESSED_LEFT = 3
const val STATE_PRESSED_RIGHT = 4
const val STATE_PRESSED_UP_LEFT = 5
const val STATE_PRESSED_UP_RIGHT = 6
const val STATE_PRESSED_DOWN_LEFT = 7
const val STATE_PRESSED_DOWN_RIGHT = 8
}
}

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -1,180 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.overlay;
import android.graphics.Rect;
import android.os.Handler;
import android.view.MotionEvent;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider;
import java.util.ArrayList;
public class InputOverlayPointer
{
public static final int MODE_DISABLED = 0;
public static final int MODE_FOLLOW = 1;
public static final int MODE_DRAG = 2;
private float mCurrentX = 0.0f;
private float mCurrentY = 0.0f;
private float mOldX = 0.0f;
private float mOldY = 0.0f;
private float mGameCenterX;
private float mGameCenterY;
private float mGameWidthHalfInv;
private float mGameHeightHalfInv;
private float mTouchStartX;
private float mTouchStartY;
private int mMode;
private boolean mRecenter;
private int mControllerIndex;
private boolean doubleTap = false;
private int mDoubleTapControl;
private int trackId = -1;
public static ArrayList<Integer> DOUBLE_TAP_OPTIONS = new ArrayList<>();
static
{
DOUBLE_TAP_OPTIONS.add(NativeLibrary.ButtonType.WIIMOTE_BUTTON_A);
DOUBLE_TAP_OPTIONS.add(NativeLibrary.ButtonType.WIIMOTE_BUTTON_B);
DOUBLE_TAP_OPTIONS.add(NativeLibrary.ButtonType.WIIMOTE_BUTTON_2);
DOUBLE_TAP_OPTIONS.add(NativeLibrary.ButtonType.CLASSIC_BUTTON_A);
}
public InputOverlayPointer(Rect surfacePosition, int doubleTapControl, int mode, boolean recenter,
int controllerIndex)
{
mDoubleTapControl = doubleTapControl;
mMode = mode;
mRecenter = recenter;
mControllerIndex = controllerIndex;
mGameCenterX = (surfacePosition.left + surfacePosition.right) / 2.0f;
mGameCenterY = (surfacePosition.top + surfacePosition.bottom) / 2.0f;
float gameWidth = surfacePosition.right - surfacePosition.left;
float gameHeight = surfacePosition.bottom - surfacePosition.top;
// Adjusting for device's black bars.
float surfaceAR = gameWidth / gameHeight;
float gameAR = NativeLibrary.GetGameAspectRatio();
if (gameAR <= surfaceAR)
{
// Black bars on left/right
gameWidth = gameHeight * gameAR;
}
else
{
// Black bars on top/bottom
gameHeight = gameWidth / gameAR;
}
mGameWidthHalfInv = 1.0f / (gameWidth * 0.5f);
mGameHeightHalfInv = 1.0f / (gameHeight * 0.5f);
}
public void onTouch(MotionEvent event)
{
int action = event.getActionMasked();
boolean firstPointer = action != MotionEvent.ACTION_POINTER_DOWN &&
action != MotionEvent.ACTION_POINTER_UP;
int pointerIndex = firstPointer ? 0 : event.getActionIndex();
switch (action)
{
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
trackId = event.getPointerId(pointerIndex);
mTouchStartX = event.getX(pointerIndex);
mTouchStartY = event.getY(pointerIndex);
touchPress();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (trackId == event.getPointerId(pointerIndex))
trackId = -1;
if (mMode == MODE_DRAG)
updateOldAxes();
if (mRecenter)
reset();
break;
}
int eventPointerIndex = event.findPointerIndex(trackId);
if (trackId == -1 || eventPointerIndex == -1)
return;
if (mMode == MODE_FOLLOW)
{
mCurrentX = (event.getX(eventPointerIndex) - mGameCenterX) * mGameWidthHalfInv;
mCurrentY = (event.getY(eventPointerIndex) - mGameCenterY) * mGameHeightHalfInv;
}
else if (mMode == MODE_DRAG)
{
mCurrentX = mOldX +
(event.getX(eventPointerIndex) - mTouchStartX) * mGameWidthHalfInv;
mCurrentY = mOldY +
(event.getY(eventPointerIndex) - mTouchStartY) * mGameHeightHalfInv;
}
}
private void touchPress()
{
if (mMode != MODE_DISABLED)
{
if (doubleTap)
{
InputOverrider.setControlState(mControllerIndex, mDoubleTapControl, 1.0);
new Handler().postDelayed(() -> InputOverrider.setControlState(mControllerIndex,
mDoubleTapControl, 0.0),
50);
}
else
{
doubleTap = true;
new Handler().postDelayed(() -> doubleTap = false, 300);
}
}
}
private void updateOldAxes()
{
mOldX = mCurrentX;
mOldY = mCurrentY;
}
private void reset()
{
mCurrentX = mCurrentY = mOldX = mOldY = 0.0f;
}
public float getX()
{
return mCurrentX;
}
public float getY()
{
return mCurrentY;
}
public void setMode(int mode)
{
mMode = mode;
if (mode == MODE_DRAG)
updateOldAxes();
}
public void setRecenter(boolean recenter)
{
mRecenter = recenter;
}
}

View File

@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.overlay
import android.graphics.Rect
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent
import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider
class InputOverlayPointer(
surfacePosition: Rect,
private val doubleTapControl: Int,
private var mode: Int,
private var recenter: Boolean,
private val controllerIndex: Int
) {
var x = 0.0f
var y = 0.0f
private var oldX = 0.0f
private var oldY = 0.0f
private val gameCenterX: Float
private val gameCenterY: Float
private val gameWidthHalfInv: Float
private val gameHeightHalfInv: Float
private var touchStartX = 0f
private var touchStartY = 0f
private var doubleTap = false
private var trackId = -1
init {
gameCenterX = (surfacePosition.left + surfacePosition.right) / 2f
gameCenterY = (surfacePosition.top + surfacePosition.bottom) / 2f
var gameWidth = (surfacePosition.right - surfacePosition.left).toFloat()
var gameHeight = (surfacePosition.bottom - surfacePosition.top).toFloat()
// Adjusting for device's black bars.
val surfaceAR = gameWidth / gameHeight
val gameAR = NativeLibrary.GetGameAspectRatio()
if (gameAR <= surfaceAR) {
// Black bars on left/right
gameWidth = gameHeight * gameAR
} else {
// Black bars on top/bottom
gameHeight = gameWidth / gameAR
}
gameWidthHalfInv = 1f / (gameWidth * 0.5f)
gameHeightHalfInv = 1f / (gameHeight * 0.5f)
}
fun onTouch(event: MotionEvent) {
val action = event.actionMasked
val firstPointer = action != MotionEvent.ACTION_POINTER_DOWN &&
action != MotionEvent.ACTION_POINTER_UP
val pointerIndex = if (firstPointer) 0 else event.actionIndex
when (action) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
trackId = event.getPointerId(pointerIndex)
touchStartX = event.getX(pointerIndex)
touchStartY = event.getY(pointerIndex)
touchPress()
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
if (trackId == event.getPointerId(pointerIndex))
trackId = -1
if (mode == MODE_DRAG)
updateOldAxes()
if (recenter)
reset()
}
}
val eventPointerIndex = event.findPointerIndex(trackId)
if (trackId == -1 || eventPointerIndex == -1)
return
if (mode == MODE_FOLLOW) {
x = (event.getX(eventPointerIndex) - gameCenterX) * gameWidthHalfInv
y = (event.getY(eventPointerIndex) - gameCenterY) * gameHeightHalfInv
} else if (mode == MODE_DRAG) {
x = oldX + (event.getX(eventPointerIndex) - touchStartX) * gameWidthHalfInv
y = oldY + (event.getY(eventPointerIndex) - touchStartY) * gameHeightHalfInv
}
}
private fun touchPress() {
if (mode != MODE_DISABLED) {
if (doubleTap) {
InputOverrider.setControlState(controllerIndex, doubleTapControl, 1.0)
Handler(Looper.myLooper()!!).postDelayed(
{
InputOverrider.setControlState(controllerIndex, doubleTapControl, 0.0)
},
50
)
} else {
doubleTap = true
Handler(Looper.myLooper()!!).postDelayed({ doubleTap = false }, 300)
}
}
}
private fun updateOldAxes() {
oldX = x
oldY = y
}
private fun reset() {
oldY = 0.0f
oldX = 0.0f
y = 0.0f
x = 0.0f
}
fun setMode(mode: Int) {
this.mode = mode
if (mode == MODE_DRAG)
updateOldAxes()
}
fun setRecenter(recenter: Boolean) {
this.recenter = recenter
}
companion object {
const val MODE_DISABLED = 0
const val MODE_FOLLOW = 1
const val MODE_DRAG = 2
@JvmField
var DOUBLE_TAP_OPTIONS = arrayListOf(
NativeLibrary.ButtonType.WIIMOTE_BUTTON_A,
NativeLibrary.ButtonType.WIIMOTE_BUTTON_B,
NativeLibrary.ButtonType.WIIMOTE_BUTTON_2,
NativeLibrary.ButtonType.CLASSIC_BUTTON_A
)
}
}