Android: Basic touchscreen controller implementation

This commit is contained in:
Connor McLaughlin
2019-12-09 01:43:37 +10:00
parent 89e9373037
commit 5d91c011a6
25 changed files with 625 additions and 2 deletions

View File

@ -22,4 +22,9 @@ public class AndroidHostInterface
public native void stopEmulationThread();
public native void surfaceChanged(Surface surface, int format, int width, int height);
// TODO: Find a better place for this.
public native void setControllerType(int index, String typeName);
public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
public static native int getControllerButtonCode(String controllerType, String buttonName);
}

View File

@ -13,6 +13,7 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.MenuItem;
import android.widget.FrameLayout;
import androidx.core.app.NavUtils;
@ -26,6 +27,11 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
*/
AndroidHostInterface mHostInterface;
/**
* Touchscreen controller overlay
*/
TouchscreenControllerView mTouchscreenController;
/**
* Whether or not the system UI should be auto-hidden after
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
@ -153,6 +159,12 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
mHostInterface = AndroidHostInterface.create();
if (mHostInterface == null)
throw new InstantiationError("Failed to create host interface");
// Create touchscreen controller.
FrameLayout activityLayout = findViewById(R.id.frameLayout);
mTouchscreenController = new TouchscreenControllerView(this);
activityLayout.addView(mTouchscreenController);
mTouchscreenController.init(0, "DigitalController", mHostInterface);
}
@Override

View File

@ -0,0 +1,160 @@
package com.github.stenzek.duckstation;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* TODO: document your custom view class.
*/
public class TouchscreenControllerButtonView extends View {
private Drawable mUnpressedDrawable;
private Drawable mPressedDrawable;
private boolean mPressed = false;
private int mButtonCode = -1;
private String mButtonName = "";
private ButtonStateChangedListener mListener;
public interface ButtonStateChangedListener
{
void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed);
}
public TouchscreenControllerButtonView(Context context) {
super(context);
init(context,null, 0);
}
public TouchscreenControllerButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs, 0);
}
public TouchscreenControllerButtonView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context,attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
// Load attributes
final TypedArray a = getContext().obtainStyledAttributes(
attrs, R.styleable.TouchscreenControllerButtonView, defStyle, 0);
if (a.hasValue(R.styleable.TouchscreenControllerButtonView_unpressedDrawable)) {
mUnpressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_unpressedDrawable);
mUnpressedDrawable.setCallback(this);
}
if (a.hasValue(R.styleable.TouchscreenControllerButtonView_pressedDrawable)) {
mPressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_pressedDrawable);
mPressedDrawable.setCallback(this);
}
a.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int contentWidth = getWidth() - paddingLeft - paddingRight;
int contentHeight = getHeight() - paddingTop - paddingBottom;
// Draw the example drawable on top of the text.
Drawable drawable = mPressed ? mPressedDrawable : mUnpressedDrawable;
if (drawable != null) {
drawable.setBounds(paddingLeft, paddingTop,
paddingLeft + contentWidth, paddingTop + contentHeight);
drawable.draw(canvas);
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
final boolean oldState = mPressed;
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
{
mPressed = true;
invalidate();
if (mListener != null && !oldState)
mListener.onButtonStateChanged(this, true);
return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
{
mPressed = false;
invalidate();
if (mListener != null && oldState)
mListener.onButtonStateChanged(this, false);
return true;
}
}
return super.onTouchEvent(event);
}
public boolean isPressed()
{
return mPressed;
}
public String getButtonName() {
return mButtonName;
}
public void setButtonName(String buttonName) {
mButtonName = buttonName;
}
public int getButtonCode()
{
return mButtonCode;
}
public void setButtonCode(int code)
{
mButtonCode = code;
}
public Drawable getPressedDrawable() {
return mPressedDrawable;
}
public void setPressedDrawable(Drawable pressedDrawable) {
mPressedDrawable = pressedDrawable;
}
public Drawable getUnpressedDrawable() {
return mUnpressedDrawable;
}
public void setUnpressedDrawable(Drawable unpressedDrawable) {
mUnpressedDrawable = unpressedDrawable;
}
public void setButtonStateChangedListener(ButtonStateChangedListener listener)
{
mListener = listener;
}
}

View File

@ -0,0 +1,80 @@
package com.github.stenzek.duckstation;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
/**
* TODO: document your custom view class.
*/
public class TouchscreenControllerView extends FrameLayout implements TouchscreenControllerButtonView.ButtonStateChangedListener {
private int mControllerIndex;
private String mControllerType;
private AndroidHostInterface mHostInterface;
public TouchscreenControllerView(Context context) {
super(context);
}
public TouchscreenControllerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TouchscreenControllerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void init(int controllerIndex, String controllerType,
AndroidHostInterface hostInterface) {
mControllerIndex = controllerIndex;
mControllerType = controllerType;
mHostInterface = hostInterface;
if (mHostInterface != null)
mHostInterface.setControllerType(controllerIndex, controllerType);
LayoutInflater inflater = LayoutInflater.from(getContext());
View view = inflater.inflate(R.layout.layout_touchscreen_controller, this, true);
// TODO: Make dynamic, editable.
linkButton(view, R.id.controller_button_up, "Up");
linkButton(view, R.id.controller_button_right, "Right");
linkButton(view, R.id.controller_button_down, "Down");
linkButton(view, R.id.controller_button_left, "Left");
linkButton(view, R.id.controller_button_triangle, "Triangle");
linkButton(view, R.id.controller_button_circle, "Circle");
linkButton(view, R.id.controller_button_cross, "Cross");
linkButton(view, R.id.controller_button_square, "Square");
}
private void linkButton(View view, int id, String buttonName)
{
TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView)view.findViewById(id);
buttonView.setButtonName(buttonName);
buttonView.setButtonStateChangedListener(this);
if (mHostInterface != null)
{
int code = mHostInterface.getControllerButtonCode(mControllerType, buttonName);
buttonView.setButtonCode(code);
Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code));
if (code < 0) {
Log.e("TouchscreenController", String.format("Unknown button name '%s' " +
"for '%s'", buttonName, mControllerType));
}
}
}
@Override
public void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed) {
if (mHostInterface == null || view.getButtonCode() < 0)
return;
mHostInterface.setControllerButtonState(mControllerIndex, view.getButtonCode(), pressed);
}
}