From 897f2dadf84a0f2ada3f7aaf6ebdba1a68d3c3ba Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 10 Oct 2020 12:48:19 +1000 Subject: [PATCH] Android: Add analog touchscreen controls --- .../duckstation/EmulationActivity.java | 207 +++++++++--------- .../TouchscreenControllerAxisView.java | 179 +++++++++++++++ .../TouchscreenControllerButtonView.java | 40 ++-- .../TouchscreenControllerView.java | 162 ++++++++++---- .../drawable/ic_controller_analog_base.xml | 21 ++ .../ic_controller_analog_stick_pressed.xml | 11 + .../ic_controller_analog_stick_unpressed.xml | 11 + ...ut_touchscreen_controller_analog_stick.xml | 136 ++++++++++++ ...t_touchscreen_controller_analog_sticks.xml | 201 +++++++++++++++++ ...layout_touchscreen_controller_digital.xml} | 0 android/app/src/main/res/values/arrays.xml | 14 +- .../app/src/main/res/xml/root_preferences.xml | 11 +- 12 files changed, 826 insertions(+), 167 deletions(-) create mode 100644 android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerAxisView.java create mode 100644 android/app/src/main/res/drawable/ic_controller_analog_base.xml create mode 100644 android/app/src/main/res/drawable/ic_controller_analog_stick_pressed.xml create mode 100644 android/app/src/main/res/drawable/ic_controller_analog_stick_unpressed.xml create mode 100644 android/app/src/main/res/layout/layout_touchscreen_controller_analog_stick.xml create mode 100644 android/app/src/main/res/layout/layout_touchscreen_controller_analog_sticks.xml rename android/app/src/main/res/layout/{layout_touchscreen_controller.xml => layout_touchscreen_controller_digital.xml} (100%) diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java index f83c646a0..a107bbedb 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java @@ -55,6 +55,12 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde 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) { Log.e("EmulationActivity", message); @@ -175,16 +181,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde mContentView.getHolder().addCallback(this); // Hook up controller input. - final String controllerType = getStringSetting("Controller1/Type", "DigitalController"); - 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)); + updateControllers(); } @Override @@ -263,51 +260,43 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde if (mGameTitle != null && !mGameTitle.isEmpty()) builder.setTitle(mGameTitle); - builder.setItems(R.array.emulation_menu, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - switch (i) + builder.setItems(R.array.emulation_menu, (dialogInterface, i) -> { + switch (i) + { + case 0: // Quick Load { - case 0: // Quick Load - { - AndroidHostInterface.getInstance().loadState(false, 0); - return; - } + AndroidHostInterface.getInstance().loadState(false, 0); + return; + } - case 1: // Quick Save - { - AndroidHostInterface.getInstance().saveState(false, 0); - return; - } + case 1: // Quick Save + { + AndroidHostInterface.getInstance().saveState(false, 0); + return; + } - case 2: // Toggle Speed Limiter - { - boolean newSetting = !getBooleanSetting("Main/SpeedLimiterEnabled", true); - setBooleanSetting("Main/SpeedLimiterEnabled", newSetting); - applySettings(); - return; - } + case 2: // Toggle Speed Limiter + { + boolean newSetting = !getBooleanSetting("Main/SpeedLimiterEnabled", true); + setBooleanSetting("Main/SpeedLimiterEnabled", newSetting); + applySettings(); + return; + } - case 3: // More Options - { - showMoreMenu(); - return; - } + case 3: // More Options + { + showMoreMenu(); + return; + } - case 4: // Quit - { - finish(); - return; - } + case 4: // Quit + { + finish(); + return; } } }); - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialogInterface) { - enableFullscreenImmersive(); - } - }); + builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive()); builder.create().show(); } @@ -316,56 +305,59 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde if (mGameTitle != null && !mGameTitle.isEmpty()) builder.setTitle(mGameTitle); - builder.setItems(R.array.emulation_more_menu, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - switch (i) + builder.setItems(R.array.emulation_more_menu, (dialogInterface, i) -> { + switch (i) + { + case 0: // Reset { - case 0: // Reset - { - AndroidHostInterface.getInstance().resetSystem(); - return; - } + AndroidHostInterface.getInstance().resetSystem(); + return; + } - case 1: // Cheats - { - showCheatsMenu(); - return; - } + case 1: // Cheats + { + showCheatsMenu(); + return; + } - case 2: // Change Disc - { - return; - } + case 2: // Change Disc + { + return; + } - case 3: // Toggle Touchscreen Controller - { - setTouchscreenControllerVisibility(!mTouchscreenControllerVisible); - return; - } + case 3: // Change Touchscreen Controller + { + showTouchscreenControllerMenu(); + return; + } - case 4: // Settings - { - Intent intent = new Intent(EmulationActivity.this, SettingsActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResult(intent, REQUEST_CODE_SETTINGS); - return; - } + case 4: // Settings + { + Intent intent = new Intent(EmulationActivity.this, SettingsActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivityForResult(intent, REQUEST_CODE_SETTINGS); + return; + } - case 5: // Quit - { - finish(); - return; - } + case 5: // Quit + { + finish(); + return; } } }); - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialogInterface) { - enableFullscreenImmersive(); - } + builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive()); + builder.create().show(); + } + + 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(); } @@ -384,18 +376,8 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde items[i] = String.format("%s %s", cc.isEnabled() ? "(ON)" : "(OFF)", cc.getName()); } - builder.setItems(items, new DialogInterface.OnClickListener() { - @Override - 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.setItems(items, (dialogInterface, i) -> AndroidHostInterface.getInstance().setCheatEnabled(i, !cheats[i].isEnabled())); + builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive()); builder.create().show(); } @@ -403,10 +385,29 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde * Touchscreen controller overlay */ TouchscreenControllerView mTouchscreenController; - private boolean mTouchscreenControllerVisible = true; - private void setTouchscreenControllerVisibility(boolean visible) { - mTouchscreenControllerVisible = visible; - mTouchscreenController.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + public void updateControllers() { + final String controllerType = getStringSetting("Controller1/Type", "DigitalController"); + 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); + } } } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerAxisView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerAxisView.java new file mode 100644 index 000000000..d425b8eb9 --- /dev/null +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerAxisView.java @@ -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; + } +} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java index 438b7b8db..7d2f8199e 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java @@ -16,8 +16,8 @@ public class TouchscreenControllerButtonView extends View { private Drawable mUnpressedDrawable; private Drawable mPressedDrawable; private boolean mPressed = false; + private int mControllerIndex = -1; private int mButtonCode = -1; - private String mButtonName = ""; public TouchscreenControllerButtonView(Context context) { super(context); @@ -56,13 +56,12 @@ public class TouchscreenControllerButtonView extends View { 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; + 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; // Draw the example drawable on top of the text. Drawable drawable = mPressed ? mPressedDrawable : mUnpressedDrawable; @@ -77,24 +76,25 @@ public class TouchscreenControllerButtonView extends View { return mPressed; } - public void setPressed(boolean pressed) { mPressed = pressed; invalidate(); } + public void setPressed(boolean pressed) { + if (pressed == mPressed) + return; - public String getButtonName() { - return mButtonName; + mPressed = pressed; + invalidate(); + updateControllerState(); } - public void setButtonName(String buttonName) { - mButtonName = buttonName; - } - - public int getButtonCode() { - return mButtonCode; - } - - public void setButtonCode(int code) { + public void setButtonCode(int controllerIndex, int code) { + mControllerIndex = controllerIndex; mButtonCode = code; } + private void updateControllerState() { + if (mButtonCode >= 0) + AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, mButtonCode, mPressed); + } + public Drawable getPressedDrawable() { return mPressedDrawable; } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java index 17df2aa85..9424ecc55 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java @@ -2,7 +2,6 @@ package com.github.stenzek.duckstation; import android.content.Context; import android.graphics.Rect; -import android.text.method.Touch; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; @@ -11,7 +10,6 @@ import android.view.View; import android.widget.FrameLayout; import java.util.ArrayList; -import java.util.HashMap; /** * TODO: document your custom view class. @@ -19,7 +17,9 @@ import java.util.HashMap; public class TouchscreenControllerView extends FrameLayout { private int mControllerIndex; private String mControllerType; + private View mMainView; private ArrayList mButtonViews = new ArrayList<>(); + private ArrayList mAxisViews = new ArrayList<>(); public TouchscreenControllerView(Context context) { super(context); @@ -33,43 +33,72 @@ public class TouchscreenControllerView extends FrameLayout { super(context, attrs, defStyle); } - public void init(int controllerIndex, String controllerType) { + public void init(int controllerIndex, String controllerType, String viewType) { mControllerIndex = controllerIndex; mControllerType = controllerType; + mButtonViews.clear(); + mAxisViews.clear(); + removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(getContext()); - View view = inflater.inflate(R.layout.layout_touchscreen_controller, this, true); - view.setOnTouchListener((view1, event) -> { + switch (viewType) + { + 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); }); - // TODO: Make dynamic, editable. - mButtonViews.clear(); - 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_l1, "L1"); - linkButton(view, R.id.controller_button_l2, "L2"); - linkButton(view, R.id.controller_button_select, "Select"); - linkButton(view, R.id.controller_button_start, "Start"); - 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"); - linkButton(view, R.id.controller_button_r1, "R1"); - linkButton(view, R.id.controller_button_r2, "R2"); + linkButton(mMainView, R.id.controller_button_up, "Up"); + linkButton(mMainView, R.id.controller_button_right, "Right"); + linkButton(mMainView, R.id.controller_button_down, "Down"); + linkButton(mMainView, R.id.controller_button_left, "Left"); + linkButton(mMainView, R.id.controller_button_l1, "L1"); + linkButton(mMainView, R.id.controller_button_l2, "L2"); + linkButton(mMainView, R.id.controller_button_select, "Select"); + linkButton(mMainView, R.id.controller_button_start, "Start"); + linkButton(mMainView, R.id.controller_button_triangle, "Triangle"); + linkButton(mMainView, R.id.controller_button_circle, "Circle"); + linkButton(mMainView, R.id.controller_button_cross, "Cross"); + linkButton(mMainView, R.id.controller_button_square, "Square"); + linkButton(mMainView, R.id.controller_button_r1, "R1"); + linkButton(mMainView, 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) { TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id); - buttonView.setButtonName(buttonName); + if (buttonView == null) + return; int code = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonName); - buttonView.setButtonCode(code); Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code)); if (code >= 0) { + buttonView.setButtonCode(mControllerIndex, code); mButtonViews.add(buttonView); } else { 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) { switch (event.getActionMasked()) { case MotionEvent.ACTION_UP: { - clearAllButtonPressedStates(); + for (TouchscreenControllerButtonView buttonView : mButtonViews) { + buttonView.setPressed(false); + } + + for (TouchscreenControllerAxisView axisView : mAxisViews) { + axisView.setUnpressed(); + } + return true; } @@ -103,13 +173,37 @@ public class TouchscreenControllerView extends FrameLayout { final int x = (int) event.getX(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) - continue; - buttonView.setPressed(pressed); - AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, buttonView.getButtonCode(), 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; + + final int pointerId = event.getPointerId(i); + 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; @@ -118,14 +212,4 @@ public class TouchscreenControllerView extends FrameLayout { return false; } - - private void clearAllButtonPressedStates() { - for (TouchscreenControllerButtonView buttonView : mButtonViews) { - if (!buttonView.isPressed()) - continue; - - AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, buttonView.getButtonCode(), false); - buttonView.setPressed(false); - } - } } diff --git a/android/app/src/main/res/drawable/ic_controller_analog_base.xml b/android/app/src/main/res/drawable/ic_controller_analog_base.xml new file mode 100644 index 000000000..d190be624 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_analog_base.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_controller_analog_stick_pressed.xml b/android/app/src/main/res/drawable/ic_controller_analog_stick_pressed.xml new file mode 100644 index 000000000..0c51a32f1 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_analog_stick_pressed.xml @@ -0,0 +1,11 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_controller_analog_stick_unpressed.xml b/android/app/src/main/res/drawable/ic_controller_analog_stick_unpressed.xml new file mode 100644 index 000000000..30e13f5ff --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_analog_stick_unpressed.xml @@ -0,0 +1,11 @@ + + + diff --git a/android/app/src/main/res/layout/layout_touchscreen_controller_analog_stick.xml b/android/app/src/main/res/layout/layout_touchscreen_controller_analog_stick.xml new file mode 100644 index 000000000..3129a0f18 --- /dev/null +++ b/android/app/src/main/res/layout/layout_touchscreen_controller_analog_stick.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/layout_touchscreen_controller_analog_sticks.xml b/android/app/src/main/res/layout/layout_touchscreen_controller_analog_sticks.xml new file mode 100644 index 000000000..891d4316f --- /dev/null +++ b/android/app/src/main/res/layout/layout_touchscreen_controller_analog_sticks.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/layout_touchscreen_controller.xml b/android/app/src/main/res/layout/layout_touchscreen_controller_digital.xml similarity index 100% rename from android/app/src/main/res/layout/layout_touchscreen_controller.xml rename to android/app/src/main/res/layout/layout_touchscreen_controller_digital.xml diff --git a/android/app/src/main/res/values/arrays.xml b/android/app/src/main/res/values/arrays.xml index bf03f2cfc..b2f274d33 100644 --- a/android/app/src/main/res/values/arrays.xml +++ b/android/app/src/main/res/values/arrays.xml @@ -136,7 +136,7 @@ Reset Cheats Change Disc - Toggle Touch Controller + Change Touchscreen Controller Settings Quit @@ -164,4 +164,16 @@ 9 10 + + None + Digital Pad + Single Analog Pad + Dual Analog Pad + + + none + digital + analog_stick + analog_sticks + diff --git a/android/app/src/main/res/xml/root_preferences.xml b/android/app/src/main/res/xml/root_preferences.xml index 86364a756..6323e7bf7 100644 --- a/android/app/src/main/res/xml/root_preferences.xml +++ b/android/app/src/main/res/xml/root_preferences.xml @@ -271,10 +271,13 @@ app:title="Enable Analog Mode On Reset" app:defaultValue="false" app:iconSpaceReserved="false" /> -