Android: Basic touchscreen controller implementation
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user