Android: Add analog touchscreen controls

This commit is contained in:
Connor McLaughlin 2020-10-10 12:48:19 +10:00
parent 4f0007dd55
commit 897f2dadf8
12 changed files with 826 additions and 167 deletions

View file

@ -55,6 +55,12 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
return mPreferences.getString(key, defaultValue); return mPreferences.getString(key, defaultValue);
} }
private void setStringSetting(String key, String value) {
SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(key, value);
editor.apply();
}
public void reportError(String message) { public void reportError(String message) {
Log.e("EmulationActivity", message); Log.e("EmulationActivity", message);
@ -175,16 +181,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
mContentView.getHolder().addCallback(this); mContentView.getHolder().addCallback(this);
// Hook up controller input. // Hook up controller input.
final String controllerType = getStringSetting("Controller1/Type", "DigitalController"); updateControllers();
Log.i("EmulationActivity", "Controller type: " + controllerType);
mContentView.initControllerKeyMapping(controllerType);
// Create touchscreen controller.
FrameLayout activityLayout = findViewById(R.id.frameLayout);
mTouchscreenController = new TouchscreenControllerView(this);
activityLayout.addView(mTouchscreenController);
mTouchscreenController.init(0, controllerType);
setTouchscreenControllerVisibility(getBooleanSetting("Controller1/EnableTouchscreenController", true));
} }
@Override @Override
@ -263,9 +260,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
if (mGameTitle != null && !mGameTitle.isEmpty()) if (mGameTitle != null && !mGameTitle.isEmpty())
builder.setTitle(mGameTitle); builder.setTitle(mGameTitle);
builder.setItems(R.array.emulation_menu, new DialogInterface.OnClickListener() { builder.setItems(R.array.emulation_menu, (dialogInterface, i) -> {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
switch (i) switch (i)
{ {
case 0: // Quick Load case 0: // Quick Load
@ -300,14 +295,8 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
return; return;
} }
} }
}
});
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
enableFullscreenImmersive();
}
}); });
builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive());
builder.create().show(); builder.create().show();
} }
@ -316,9 +305,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
if (mGameTitle != null && !mGameTitle.isEmpty()) if (mGameTitle != null && !mGameTitle.isEmpty())
builder.setTitle(mGameTitle); builder.setTitle(mGameTitle);
builder.setItems(R.array.emulation_more_menu, new DialogInterface.OnClickListener() { builder.setItems(R.array.emulation_more_menu, (dialogInterface, i) -> {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
switch (i) switch (i)
{ {
case 0: // Reset case 0: // Reset
@ -338,9 +325,9 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
return; return;
} }
case 3: // Toggle Touchscreen Controller case 3: // Change Touchscreen Controller
{ {
setTouchscreenControllerVisibility(!mTouchscreenControllerVisible); showTouchscreenControllerMenu();
return; return;
} }
@ -358,14 +345,19 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
return; return;
} }
} }
}
}); });
builder.setOnDismissListener(new DialogInterface.OnDismissListener() { builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive());
@Override builder.create().show();
public void onDismiss(DialogInterface dialogInterface) {
enableFullscreenImmersive();
} }
private void showTouchscreenControllerMenu() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setItems(R.array.settings_touchscreen_controller_view_entries, (dialogInterface, i) -> {
String[] values = getResources().getStringArray(R.array.settings_touchscreen_controller_view_values);
setStringSetting("Controller1/TouchscreenControllerView", values[i]);
updateControllers();
}); });
builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive());
builder.create().show(); builder.create().show();
} }
@ -384,18 +376,8 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
items[i] = String.format("%s %s", cc.isEnabled() ? "(ON)" : "(OFF)", cc.getName()); items[i] = String.format("%s %s", cc.isEnabled() ? "(ON)" : "(OFF)", cc.getName());
} }
builder.setItems(items, new DialogInterface.OnClickListener() { builder.setItems(items, (dialogInterface, i) -> AndroidHostInterface.getInstance().setCheatEnabled(i, !cheats[i].isEnabled()));
@Override builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive());
public void onClick(DialogInterface dialogInterface, int i) {
AndroidHostInterface.getInstance().setCheatEnabled(i, !cheats[i].isEnabled());
}
});
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
enableFullscreenImmersive();
}
});
builder.create().show(); builder.create().show();
} }
@ -403,10 +385,29 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
* Touchscreen controller overlay * Touchscreen controller overlay
*/ */
TouchscreenControllerView mTouchscreenController; TouchscreenControllerView mTouchscreenController;
private boolean mTouchscreenControllerVisible = true;
private void setTouchscreenControllerVisibility(boolean visible) { public void updateControllers() {
mTouchscreenControllerVisible = visible; final String controllerType = getStringSetting("Controller1/Type", "DigitalController");
mTouchscreenController.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); final String viewType = getStringSetting("Controller1/TouchscreenControllerView", "digital");
final FrameLayout activityLayout = findViewById(R.id.frameLayout);
Log.i("EmulationActivity", "Controller type: " + controllerType);
Log.i("EmulationActivity", "View type: " + viewType);
mContentView.initControllerKeyMapping(controllerType);
if (controllerType == "none" || viewType == "none") {
if (mTouchscreenController != null) {
activityLayout.removeView(mTouchscreenController);
mTouchscreenController = null;
}
} else {
if (mTouchscreenController == null) {
mTouchscreenController = new TouchscreenControllerView(this);
activityLayout.addView(mTouchscreenController);
}
mTouchscreenController.init(0, controllerType, viewType);
}
} }
} }

View file

@ -0,0 +1,179 @@
package com.github.stenzek.duckstation;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
public class TouchscreenControllerAxisView extends View {
private Drawable mBaseDrawable;
private Drawable mStickUnpressedDrawable;
private Drawable mStickPressedDrawable;
private boolean mPressed = false;
private int mPointerId = 0;
private float mXValue = 0.0f;
private float mYValue = 0.0f;
private int mDrawXPos = 0;
private int mDrawYPos = 0;
private int mControllerIndex = -1;
private int mXAxisCode = -1;
private int mYAxisCode = -1;
private int mLeftButtonCode = -1;
private int mRightButtonCode = -1;
private int mUpButtonCode = -1;
private int mDownButtonCode = -1;
public TouchscreenControllerAxisView(Context context) {
super(context);
init();
}
public TouchscreenControllerAxisView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TouchscreenControllerAxisView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mBaseDrawable = getContext().getDrawable(R.drawable.ic_controller_analog_base);
mBaseDrawable.setCallback(this);
mStickUnpressedDrawable = getContext().getDrawable(R.drawable.ic_controller_analog_stick_unpressed);
mStickUnpressedDrawable.setCallback(this);
mStickPressedDrawable = getContext().getDrawable(R.drawable.ic_controller_analog_stick_pressed);
mStickPressedDrawable.setCallback(this);
}
public void setControllerAxis(int controllerIndex, int xCode, int yCode) {
mControllerIndex = controllerIndex;
mXAxisCode = xCode;
mYAxisCode = yCode;
mLeftButtonCode = -1;
mRightButtonCode = -1;
mUpButtonCode = -1;
mDownButtonCode = -1;
}
public void setControllerButtons(int controllerIndex, int leftCode, int rightCode, int upCode, int downCode) {
mControllerIndex = controllerIndex;
mXAxisCode = -1;
mYAxisCode = -1;
mLeftButtonCode = leftCode;
mRightButtonCode = rightCode;
mUpButtonCode = upCode;
mDownButtonCode = downCode;
}
public void setUnpressed() {
if (!mPressed && mXValue == 0.0f && mYValue == 0.0f)
return;
mPressed = false;
mXValue = 0.0f;
mYValue = 0.0f;
mDrawXPos = 0;
mDrawYPos = 0;
invalidate();
updateControllerState();
}
public void setPressed(int pointerId, float pointerX, float pointerY) {
final float dx = pointerX - (float)(getX() + (float)(getWidth() / 2));
final float dy = pointerY - (float)(getY() + (float)(getHeight() / 2));
// Log.i("SetPressed", String.format("px=%f,py=%f dx=%f,dy=%f", pointerX, pointerY, dx, dy));
final float pointerDistance = Math.max(Math.abs(dx), Math.abs(dy));
final float angle = (float)Math.atan2((double)dy, (double)dx);
final float maxDistance = (float)Math.min((getWidth() - getPaddingLeft() - getPaddingRight()) / 2, (getHeight() - getPaddingTop() - getPaddingBottom()) / 2);
final float length = Math.min(pointerDistance / maxDistance, 1.0f);
// Log.i("SetPressed", String.format("pointerDist=%f,angle=%f,w=%d,h=%d,maxDist=%f,length=%f", pointerDistance, angle, getWidth(), getHeight(), maxDistance, length));
final float xValue = (float)Math.cos((double)angle) * length;
final float yValue = (float)Math.sin((double)angle) * length;
mDrawXPos = (int)(xValue * maxDistance);
mDrawYPos = (int)(yValue * maxDistance);
boolean doUpdate = (pointerId != mPointerId || !mPressed || (xValue != mXValue || yValue != mYValue));
mPointerId = pointerId;
mPressed = true;
mXValue = xValue;
mYValue = yValue;
// Log.i("SetPressed", String.format("xval=%f,yval=%f,drawX=%d,drawY=%d", mXValue, mYValue, mDrawXPos, mDrawYPos));
if (doUpdate) {
invalidate();
updateControllerState();
}
}
private void updateControllerState() {
final float BUTTON_THRESHOLD = 0.33f;
AndroidHostInterface hostInterface = AndroidHostInterface.getInstance();
if (mXAxisCode >= 0)
hostInterface.setControllerAxisState(mControllerIndex, mXAxisCode, mXValue);
if (mYAxisCode >= 0)
hostInterface.setControllerAxisState(mControllerIndex, mYAxisCode, mYValue);
if (mLeftButtonCode >= 0)
hostInterface.setControllerButtonState(mControllerIndex, mLeftButtonCode, (mXValue <= -BUTTON_THRESHOLD));
if (mRightButtonCode >= 0)
hostInterface.setControllerButtonState(mControllerIndex, mRightButtonCode, (mXValue >= BUTTON_THRESHOLD));
if (mUpButtonCode >= 0)
hostInterface.setControllerButtonState(mControllerIndex, mUpButtonCode, (mYValue <= -BUTTON_THRESHOLD));
if (mDownButtonCode >= 0)
hostInterface.setControllerButtonState(mControllerIndex, mDownButtonCode, (mYValue >= BUTTON_THRESHOLD));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int paddingRight = getPaddingRight();
final int paddingBottom = getPaddingBottom();
final int contentWidth = getWidth() - paddingLeft - paddingRight;
final int contentHeight = getHeight() - paddingTop - paddingBottom;
mBaseDrawable.setBounds(paddingLeft, paddingTop,
paddingLeft + contentWidth, paddingTop + contentHeight);
mBaseDrawable.draw(canvas);
final int stickWidth = contentWidth / 3;
final int stickHeight = contentHeight / 3;
final int halfStickWidth = stickWidth / 2;
final int halfStickHeight = stickHeight / 2;
final int centerX = getWidth() / 2;
final int centerY = getHeight() / 2;
final int drawX = centerX + mDrawXPos;
final int drawY = centerY + mDrawYPos;
Drawable stickDrawable = mPressed ? mStickPressedDrawable : mStickUnpressedDrawable;
stickDrawable.setBounds(drawX - halfStickWidth, drawY - halfStickHeight, drawX + halfStickWidth, drawY + halfStickHeight);
stickDrawable.draw(canvas);
}
public boolean isPressed() {
return mPressed;
}
public boolean hasPointerId() {
return mPointerId >= 0;
}
public int getPointerId() {
return mPointerId;
}
public void setPointerId(int mPointerId) {
this.mPointerId = mPointerId;
}
}

View file

@ -16,8 +16,8 @@ public class TouchscreenControllerButtonView extends View {
private Drawable mUnpressedDrawable; private Drawable mUnpressedDrawable;
private Drawable mPressedDrawable; private Drawable mPressedDrawable;
private boolean mPressed = false; private boolean mPressed = false;
private int mControllerIndex = -1;
private int mButtonCode = -1; private int mButtonCode = -1;
private String mButtonName = "";
public TouchscreenControllerButtonView(Context context) { public TouchscreenControllerButtonView(Context context) {
super(context); super(context);
@ -56,13 +56,12 @@ public class TouchscreenControllerButtonView extends View {
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
int paddingLeft = getPaddingLeft(); final int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop(); final int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight(); final int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom(); final int paddingBottom = getPaddingBottom();
final int contentWidth = getWidth() - paddingLeft - paddingRight;
int contentWidth = getWidth() - paddingLeft - paddingRight; final int contentHeight = getHeight() - paddingTop - paddingBottom;
int contentHeight = getHeight() - paddingTop - paddingBottom;
// Draw the example drawable on top of the text. // Draw the example drawable on top of the text.
Drawable drawable = mPressed ? mPressedDrawable : mUnpressedDrawable; Drawable drawable = mPressed ? mPressedDrawable : mUnpressedDrawable;
@ -77,24 +76,25 @@ public class TouchscreenControllerButtonView extends View {
return mPressed; return mPressed;
} }
public void setPressed(boolean pressed) { mPressed = pressed; invalidate(); } public void setPressed(boolean pressed) {
if (pressed == mPressed)
return;
public String getButtonName() { mPressed = pressed;
return mButtonName; invalidate();
updateControllerState();
} }
public void setButtonName(String buttonName) { public void setButtonCode(int controllerIndex, int code) {
mButtonName = buttonName; mControllerIndex = controllerIndex;
}
public int getButtonCode() {
return mButtonCode;
}
public void setButtonCode(int code) {
mButtonCode = code; mButtonCode = code;
} }
private void updateControllerState() {
if (mButtonCode >= 0)
AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, mButtonCode, mPressed);
}
public Drawable getPressedDrawable() { public Drawable getPressedDrawable() {
return mPressedDrawable; return mPressedDrawable;
} }

View file

@ -2,7 +2,6 @@ package com.github.stenzek.duckstation;
import android.content.Context; import android.content.Context;
import android.graphics.Rect; import android.graphics.Rect;
import android.text.method.Touch;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -11,7 +10,6 @@ import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
/** /**
* TODO: document your custom view class. * TODO: document your custom view class.
@ -19,7 +17,9 @@ import java.util.HashMap;
public class TouchscreenControllerView extends FrameLayout { public class TouchscreenControllerView extends FrameLayout {
private int mControllerIndex; private int mControllerIndex;
private String mControllerType; private String mControllerType;
private View mMainView;
private ArrayList<TouchscreenControllerButtonView> mButtonViews = new ArrayList<>(); private ArrayList<TouchscreenControllerButtonView> mButtonViews = new ArrayList<>();
private ArrayList<TouchscreenControllerAxisView> mAxisViews = new ArrayList<>();
public TouchscreenControllerView(Context context) { public TouchscreenControllerView(Context context) {
super(context); super(context);
@ -33,43 +33,72 @@ public class TouchscreenControllerView extends FrameLayout {
super(context, attrs, defStyle); super(context, attrs, defStyle);
} }
public void init(int controllerIndex, String controllerType) { public void init(int controllerIndex, String controllerType, String viewType) {
mControllerIndex = controllerIndex; mControllerIndex = controllerIndex;
mControllerType = controllerType; mControllerType = controllerType;
mButtonViews.clear();
mAxisViews.clear();
removeAllViews();
LayoutInflater inflater = LayoutInflater.from(getContext()); LayoutInflater inflater = LayoutInflater.from(getContext());
View view = inflater.inflate(R.layout.layout_touchscreen_controller, this, true); switch (viewType)
view.setOnTouchListener((view1, event) -> { {
case "none":
break;
case "digital":
mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_digital, this, true);
break;
case "analog_stick":
mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_analog_stick, this, true);
break;
case "analog_sticks":
mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_analog_sticks, this, true);
break;
default:
mMainView = null;
break;
}
mMainView.setOnTouchListener((view1, event) -> {
return handleTouchEvent(event); return handleTouchEvent(event);
}); });
// TODO: Make dynamic, editable. linkButton(mMainView, R.id.controller_button_up, "Up");
mButtonViews.clear(); linkButton(mMainView, R.id.controller_button_right, "Right");
linkButton(view, R.id.controller_button_up, "Up"); linkButton(mMainView, R.id.controller_button_down, "Down");
linkButton(view, R.id.controller_button_right, "Right"); linkButton(mMainView, R.id.controller_button_left, "Left");
linkButton(view, R.id.controller_button_down, "Down"); linkButton(mMainView, R.id.controller_button_l1, "L1");
linkButton(view, R.id.controller_button_left, "Left"); linkButton(mMainView, R.id.controller_button_l2, "L2");
linkButton(view, R.id.controller_button_l1, "L1"); linkButton(mMainView, R.id.controller_button_select, "Select");
linkButton(view, R.id.controller_button_l2, "L2"); linkButton(mMainView, R.id.controller_button_start, "Start");
linkButton(view, R.id.controller_button_select, "Select"); linkButton(mMainView, R.id.controller_button_triangle, "Triangle");
linkButton(view, R.id.controller_button_start, "Start"); linkButton(mMainView, R.id.controller_button_circle, "Circle");
linkButton(view, R.id.controller_button_triangle, "Triangle"); linkButton(mMainView, R.id.controller_button_cross, "Cross");
linkButton(view, R.id.controller_button_circle, "Circle"); linkButton(mMainView, R.id.controller_button_square, "Square");
linkButton(view, R.id.controller_button_cross, "Cross"); linkButton(mMainView, R.id.controller_button_r1, "R1");
linkButton(view, R.id.controller_button_square, "Square"); linkButton(mMainView, R.id.controller_button_r2, "R2");
linkButton(view, R.id.controller_button_r1, "R1");
linkButton(view, R.id.controller_button_r2, "R2"); if (!linkAxis(mMainView, R.id.controller_axis_left, "Left"))
linkAxisToButtons(mMainView, R.id.controller_axis_left, "");
linkAxis(mMainView, R.id.controller_axis_right, "Right");
} }
private void linkButton(View view, int id, String buttonName) { private void linkButton(View view, int id, String buttonName) {
TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id); TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id);
buttonView.setButtonName(buttonName); if (buttonView == null)
return;
int code = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonName); int code = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonName);
buttonView.setButtonCode(code);
Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code)); Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code));
if (code >= 0) { if (code >= 0) {
buttonView.setButtonCode(mControllerIndex, code);
mButtonViews.add(buttonView); mButtonViews.add(buttonView);
} else { } else {
Log.e("TouchscreenController", String.format("Unknown button name '%s' " + Log.e("TouchscreenController", String.format("Unknown button name '%s' " +
@ -77,12 +106,53 @@ public class TouchscreenControllerView extends FrameLayout {
} }
} }
private boolean linkAxis(View view, int id, String axisName) {
TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id);
if (axisView == null)
return false;
int xCode = AndroidHostInterface.getInstance().getControllerAxisCode(mControllerType, axisName + "X");
int yCode = AndroidHostInterface.getInstance().getControllerAxisCode(mControllerType, axisName + "Y");
Log.i("TouchscreenController", String.format("%s -> %d/%d", axisName, xCode, yCode));
if (xCode < 0 && yCode < 0)
return false;
axisView.setControllerAxis(mControllerIndex, xCode, yCode);
mAxisViews.add(axisView);
return true;
}
private boolean linkAxisToButtons(View view, int id, String buttonPrefix) {
TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id);
if (axisView == null)
return false;
int leftCode = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonPrefix + "Left");
int rightCode = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonPrefix + "Right");
int upCode = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonPrefix + "Up");
int downCode = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonPrefix + "Down");
Log.i("TouchscreenController", String.format("%s(ButtonAxis) -> %d,%d,%d,%d", buttonPrefix, leftCode, rightCode, upCode, downCode));
if (leftCode < 0 && rightCode < 0 && upCode < 0 && downCode < 0)
return false;
axisView.setControllerButtons(mControllerIndex, leftCode, rightCode, upCode, downCode);
mAxisViews.add(axisView);
return true;
}
private boolean handleTouchEvent(MotionEvent event) { private boolean handleTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) switch (event.getActionMasked())
{ {
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
{ {
clearAllButtonPressedStates(); for (TouchscreenControllerButtonView buttonView : mButtonViews) {
buttonView.setPressed(false);
}
for (TouchscreenControllerAxisView axisView : mAxisViews) {
axisView.setUnpressed();
}
return true; return true;
} }
@ -103,13 +173,37 @@ public class TouchscreenControllerView extends FrameLayout {
final int x = (int) event.getX(i); final int x = (int) event.getX(i);
final int y = (int) event.getY(i); final int y = (int) event.getY(i);
pressed |= rect.contains(x, y); if (rect.contains(x, y)) {
buttonView.setPressed(true);
pressed = true;
break;
} }
if (buttonView.isPressed() == pressed) }
if (!pressed)
buttonView.setPressed(pressed);
}
for (TouchscreenControllerAxisView axisView : mAxisViews) {
axisView.getHitRect(rect);
boolean pressed = false;
for (int i = 0; i < pointerCount; i++) {
if (i == liftedPointerIndex)
continue; continue;
buttonView.setPressed(pressed); final int pointerId = event.getPointerId(i);
AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, buttonView.getButtonCode(), pressed); final int x = (int) event.getX(i);
final int y = (int) event.getY(i);
if ((rect.contains(x, y) && !axisView.isPressed()) ||
(axisView.isPressed() && axisView.getPointerId() == pointerId)) {
axisView.setPressed(pointerId, x, y);
pressed = true;
break;
}
}
if (!pressed)
axisView.setUnpressed();
} }
return true; return true;
@ -118,14 +212,4 @@ public class TouchscreenControllerView extends FrameLayout {
return false; return false;
} }
private void clearAllButtonPressedStates() {
for (TouchscreenControllerButtonView buttonView : mButtonViews) {
if (!buttonView.isPressed())
continue;
AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, buttonView.getButtonCode(), false);
buttonView.setPressed(false);
}
}
} }

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="194.89dp"
android:height="194.89dp"
android:viewportWidth="194.89"
android:viewportHeight="194.89">
<path
android:pathData="M194.89,97.445A97.445,97.445 0,0 1,97.445 194.89,97.445 97.445,0 0,1 0,97.445 97.445,97.445 0,0 1,97.445 0,97.445 97.445,0 0,1 194.89,97.445Z"
android:strokeAlpha="0.50645"
android:fillColor="#1a1a1a"
android:fillAlpha="0.504414"/>
<path
android:pathData="M178.82,97.445A81.381,81.381 0,0 1,97.439 178.826,81.381 81.381,0 0,1 16.058,97.445 81.381,81.381 0,0 1,97.439 16.064,81.381 81.381,0 0,1 178.82,97.445Z"
android:strokeAlpha="0.50645"
android:fillColor="#333333"
android:fillAlpha="0.504414"/>
<path
android:pathData="M159.05,97.445A61.609,61.609 0,0 1,97.441 159.054,61.609 61.609,0 0,1 35.832,97.445 61.609,61.609 0,0 1,97.441 35.836,61.609 61.609,0 0,1 159.05,97.445Z"
android:strokeAlpha="0.50645"
android:fillColor="#1a1a1a"
android:fillAlpha="0.504414"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="191.756dp"
android:height="191.756dp"
android:viewportWidth="191.756"
android:viewportHeight="191.756">
<path
android:pathData="M191.756,95.878A95.878,95.878 0,0 1,95.878 191.756,95.878 95.878,0 0,1 0,95.878 95.878,95.878 0,0 1,95.878 0,95.878 95.878,0 0,1 191.756,95.878Z"
android:strokeAlpha="0.8"
android:fillColor="#666666"
android:fillAlpha="0.796784"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="191.756dp"
android:height="191.756dp"
android:viewportWidth="191.756"
android:viewportHeight="191.756">
<path
android:pathData="M191.756,95.878A95.878,95.878 0,0 1,95.878 191.756,95.878 95.878,0 0,1 0,95.878 95.878,95.878 0,0 1,95.878 0,95.878 95.878,0 0,1 191.756,95.878Z"
android:strokeAlpha="0.50645"
android:fillColor="#666666"
android:fillAlpha="0.504414"/>
</vector>

View file

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_r2"
android:layout_width="70dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_r2_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_r2_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_r1"
android:layout_width="70dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_r1_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_r1_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_l1"
android:layout_width="70dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_l1_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_l1_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_l2"
android:layout_width="70dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_l2_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_l2_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_start"
android:layout_width="40dp"
android:layout_height="25dp"
android:layout_marginStart="80dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_start_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_start_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_select"
android:layout_width="40dp"
android:layout_height="25dp"
android:layout_marginEnd="50dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_select_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_select_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_cross"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="70dp"
android:layout_marginBottom="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_cross_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_cross_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_square"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="120dp"
android:layout_marginBottom="80dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_square_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_square_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_triangle"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="70dp"
android:layout_marginBottom="130dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_triangle_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_triangle_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_circle"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="80dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_circle_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_circle_button" />
<com.github.stenzek.duckstation.TouchscreenControllerAxisView
android:id="@+id/controller_axis_left"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="30dp"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,201 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_r2"
android:layout_width="70dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="260dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_r2_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_r2_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_r1"
android:layout_width="70dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="200dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_r1_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_r1_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_l1"
android:layout_width="70dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="200dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_l1_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_l1_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_l2"
android:layout_width="70dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="260dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_l2_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_l2_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_start"
android:layout_width="40dp"
android:layout_height="25dp"
android:layout_marginStart="80dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_start_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_start_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_select"
android:layout_width="40dp"
android:layout_height="25dp"
android:layout_marginEnd="50dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_select_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_select_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_cross"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="70dp"
android:layout_marginBottom="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_cross_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_cross_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_square"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="120dp"
android:layout_marginBottom="80dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_square_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_square_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_triangle"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="70dp"
android:layout_marginBottom="130dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_triangle_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_triangle_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_circle"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="80dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_circle_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_circle_button" />
<com.github.stenzek.duckstation.TouchscreenControllerAxisView
android:id="@+id/controller_axis_left"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="30dp"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.github.stenzek.duckstation.TouchscreenControllerAxisView
android:id="@+id/controller_axis_right"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="300dp"
android:paddingLeft="20dp"
android:paddingTop="20dp"
android:paddingRight="20dp"
android:paddingBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_left"
android:layout_width="50dp"
android:layout_height="150dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="300dp"
android:paddingTop="50dp"
android:paddingBottom="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_left_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_left_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_down"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="300dp"
android:paddingStart="50dp"
android:paddingEnd="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_down_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_down_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_right"
android:layout_width="50dp"
android:layout_height="150dp"
android:layout_marginStart="120dp"
android:layout_marginBottom="300dp"
android:paddingTop="50dp"
android:paddingBottom="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_right_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_right_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_up"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="400dp"
android:paddingStart="50dp"
android:paddingEnd="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_up_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_up_button" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -136,7 +136,7 @@
<item>Reset</item> <item>Reset</item>
<item>Cheats</item> <item>Cheats</item>
<item>Change Disc</item> <item>Change Disc</item>
<item>Toggle Touch Controller</item> <item>Change Touchscreen Controller</item>
<item>Settings</item> <item>Settings</item>
<item>Quit</item> <item>Quit</item>
</string-array> </string-array>
@ -164,4 +164,16 @@
<item>9</item> <item>9</item>
<item>10</item> <item>10</item>
</string-array> </string-array>
<string-array name="settings_touchscreen_controller_view_entries">
<item>None</item>
<item>Digital Pad</item>
<item>Single Analog Pad</item>
<item>Dual Analog Pad</item>
</string-array>
<string-array name="settings_touchscreen_controller_view_values">
<item>none</item>
<item>digital</item>
<item>analog_stick</item>
<item>analog_sticks</item>
</string-array>
</resources> </resources>

View file

@ -271,10 +271,13 @@
app:title="Enable Analog Mode On Reset" app:title="Enable Analog Mode On Reset"
app:defaultValue="false" app:defaultValue="false"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<SwitchPreferenceCompat <ListPreference
app:key="Controller1/EnableTouchscreenController" app:key="Controller1/TouchscreenControllerView"
app:title="Display Touchscreen Controller" app:title="Touchscreen Controller View"
app:defaultValue="true" app:entries="@array/settings_touchscreen_controller_view_entries"
app:entryValues="@array/settings_touchscreen_controller_view_values"
app:defaultValue="digital"
app:useSimpleSummaryProvider="true"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
</PreferenceCategory> </PreferenceCategory>