From 20ed6913a9402d0411f0f5d809bb8e67dfd01b34 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 10 Apr 2021 18:51:09 +1000 Subject: [PATCH] Android: Support scaling touchscreen buttons --- .../duckstation/EmulationActivity.java | 7 +- .../TouchscreenControllerAxisView.java | 4 +- .../TouchscreenControllerDPadView.java | 9 +- .../TouchscreenControllerView.java | 167 ++++++++++++++---- .../layout_touchscreen_controller_edit.xml | 15 +- .../app/src/main/res/values-pt-rBR/arrays.xml | 13 +- android/app/src/main/res/values-ru/arrays.xml | 11 +- android/app/src/main/res/values/arrays.xml | 11 +- android/app/src/main/res/values/strings.xml | 1 + 9 files changed, 181 insertions(+), 57 deletions(-) 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 43960e0eb..11e9bfb8e 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 @@ -630,13 +630,16 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde } break; - case 3: // Edit Layout + case 3: // Edit Positions + case 4: // Edit Scale { if (mTouchscreenController != null) { // we deliberately don't call onMenuClosed() here to keep the system paused. // but we need to re-enable immersive mode to get proper editing. enableFullscreenImmersive(); - mTouchscreenController.startLayoutEditing(); + mTouchscreenController.startLayoutEditing( + (i == 4) ? TouchscreenControllerView.EditMode.SCALE : + TouchscreenControllerView.EditMode.POSITION); } else { // no controller onMenuClosed(); 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 index ec6db05f8..eca099791 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerAxisView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerAxisView.java @@ -96,8 +96,8 @@ public final class TouchscreenControllerAxisView extends View { } 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)); + final float dx = (pointerX / getScaleX()) - (float) (getWidth() / 2); + final float dy = (pointerY / getScaleY()) - (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)); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerDPadView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerDPadView.java index e81c3c416..28cc6f983 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerDPadView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerDPadView.java @@ -4,6 +4,7 @@ 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 final class TouchscreenControllerDPadView extends View { @@ -88,9 +89,9 @@ public final class TouchscreenControllerDPadView extends View { invalidate(); } - public void setPressed(int pointerId, float pointerX, float pointerY) { - final int posX = (int)(pointerX - getX()); - final int posY = (int)(pointerY - getY()); + public void setPressed(int pointerId, int pointerX, int pointerY) { + final int posX = (int)(pointerX / getScaleX()); + final int posY = (int)(pointerY / getScaleY()); boolean doUpdate = (pointerId != mPointerId || !mPressed || (posX != mPointerX || posY != mPointerY)); mPointerId = pointerId; @@ -106,7 +107,7 @@ public final class TouchscreenControllerDPadView extends View { private void updateControllerState() { final int subX = mPointerX / (getWidth() / 3); - final int subY = mPointerY / (getWidth() / 3); + final int subY = mPointerY / (getHeight() / 3); mDirectionStates[DIRECTION_UP] = (mPressed && subY == 0); mDirectionStates[DIRECTION_RIGHT] = (mPressed && subX == 2); 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 a17c099c8..6b61b6a36 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 @@ -29,6 +29,15 @@ import java.util.HashMap; public class TouchscreenControllerView extends FrameLayout { public static final int DEFAULT_OPACITY = 100; + public static final float MIN_VIEW_SCALE = 0.25f; + public static final float MAX_VIEW_SCALE = 10.0f; + + public enum EditMode { + NONE, + POSITION, + SCALE + } + private int mControllerIndex; private String mControllerType; private String mViewType; @@ -39,11 +48,12 @@ public class TouchscreenControllerView extends FrameLayout { private int mPointerButtonCode = -1; private boolean mHapticFeedback; private String mLayoutOrientation; - private boolean mEditingLayout = false; + private EditMode mEditMode = EditMode.NONE; private View mMovingView = null; private String mMovingName = null; private float mMovingLastX = 0.0f; private float mMovingLastY = 0.0f; + private float mMovingLastScale = 0.0f; private ConstraintLayout mEditLayout = null; private int mOpacity = 100; private Map mGlidePairs = new HashMap<>(); @@ -70,15 +80,20 @@ public class TouchscreenControllerView extends FrameLayout { return String.format("TouchscreenController/%s/%s%sYTranslation", mViewType, name, mLayoutOrientation); } + private String getConfigKeyForScale(String name) { + return String.format("TouchscreenController/%s/%s%sScale", mViewType, name, mLayoutOrientation); + } + private String getConfigKeyForVisibility(String name) { return String.format("TouchscreenController/%s/%s%sVisible", mViewType, name, mLayoutOrientation); } - private void saveTranslationForButton(String name, float xTranslation, float yTranslation) { + private void saveSettingsForButton(String name, View view) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); final SharedPreferences.Editor editor = prefs.edit(); - editor.putFloat(getConfigKeyForXTranslation(name), xTranslation); - editor.putFloat(getConfigKeyForYTranslation(name), yTranslation); + editor.putFloat(getConfigKeyForXTranslation(name), view.getTranslationX()); + editor.putFloat(getConfigKeyForYTranslation(name), view.getTranslationY()); + editor.putFloat(getConfigKeyForScale(name), view.getScaleX()); editor.commit(); } @@ -96,22 +111,31 @@ public class TouchscreenControllerView extends FrameLayout { for (TouchscreenControllerButtonView buttonView : mButtonViews) { editor.remove(getConfigKeyForXTranslation(buttonView.getConfigName())); editor.remove(getConfigKeyForYTranslation(buttonView.getConfigName())); + editor.remove(getConfigKeyForScale(buttonView.getConfigName())); buttonView.setTranslationX(0.0f); buttonView.setTranslationY(0.0f); + buttonView.setScaleX(1.0f); + buttonView.setScaleY(1.0f); } for (TouchscreenControllerAxisView axisView : mAxisViews) { editor.remove(getConfigKeyForXTranslation(axisView.getConfigName())); editor.remove(getConfigKeyForYTranslation(axisView.getConfigName())); + editor.remove(getConfigKeyForScale(axisView.getConfigName())); axisView.setTranslationX(0.0f); axisView.setTranslationY(0.0f); + axisView.setScaleX(1.0f); + axisView.setScaleY(1.0f); } if (mDPadView != null) { editor.remove(getConfigKeyForXTranslation(mDPadView.getConfigName())); editor.remove(getConfigKeyForYTranslation(mDPadView.getConfigName())); + editor.remove(getConfigKeyForScale(mDPadView.getConfigName())); mDPadView.setTranslationX(0.0f); mDPadView.setTranslationY(0.0f); + mDPadView.setScaleX(1.0f); + mDPadView.setScaleY(1.0f); } editor.commit(); @@ -125,6 +149,8 @@ public class TouchscreenControllerView extends FrameLayout { try { buttonView.setTranslationX(prefs.getFloat(getConfigKeyForXTranslation(buttonView.getConfigName()), 0.0f)); buttonView.setTranslationY(prefs.getFloat(getConfigKeyForYTranslation(buttonView.getConfigName()), 0.0f)); + buttonView.setScaleX(prefs.getFloat(getConfigKeyForScale(buttonView.getConfigName()), 1.0f)); + buttonView.setScaleY(prefs.getFloat(getConfigKeyForScale(buttonView.getConfigName()), 1.0f)); //Log.i("TouchscreenController", String.format("Translation for %s %f %f", buttonView.getConfigName(), // buttonView.getTranslationX(), buttonView.getTranslationY())); @@ -139,6 +165,8 @@ public class TouchscreenControllerView extends FrameLayout { try { axisView.setTranslationX(prefs.getFloat(getConfigKeyForXTranslation(axisView.getConfigName()), 0.0f)); axisView.setTranslationY(prefs.getFloat(getConfigKeyForYTranslation(axisView.getConfigName()), 0.0f)); + axisView.setScaleX(prefs.getFloat(getConfigKeyForScale(axisView.getConfigName()), 1.0f)); + axisView.setScaleY(prefs.getFloat(getConfigKeyForScale(axisView.getConfigName()), 1.0f)); final boolean visible = prefs.getBoolean(getConfigKeyForVisibility(axisView.getConfigName()), axisView.getDefaultVisibility()); axisView.setVisibility(visible ? VISIBLE : INVISIBLE); @@ -151,6 +179,8 @@ public class TouchscreenControllerView extends FrameLayout { try { mDPadView.setTranslationX(prefs.getFloat(getConfigKeyForXTranslation(mDPadView.getConfigName()), 0.0f)); mDPadView.setTranslationY(prefs.getFloat(getConfigKeyForYTranslation(mDPadView.getConfigName()), 0.0f)); + mDPadView.setScaleX(prefs.getFloat(getConfigKeyForScale(mDPadView.getConfigName()), 1.0f)); + mDPadView.setScaleY(prefs.getFloat(getConfigKeyForScale(mDPadView.getConfigName()), 1.0f)); final boolean visible = prefs.getBoolean(getConfigKeyForVisibility(mDPadView.getConfigName()), mDPadView.getDefaultVisibility()); mDPadView.setVisibility(visible ? VISIBLE : INVISIBLE); @@ -216,7 +246,7 @@ public class TouchscreenControllerView extends FrameLayout { mHapticFeedback = hapticFeedback; mLayoutOrientation = getOrientationString(); - if (mEditingLayout) + if (mEditMode != EditMode.NONE) endLayoutEditing(); mButtonViews.clear(); @@ -253,7 +283,7 @@ public class TouchscreenControllerView extends FrameLayout { return; mMainView.setOnTouchListener((view1, event) -> { - if (mEditingLayout) + if (mEditMode != EditMode.NONE) return handleEditingTouchEvent(event); else return handleTouchEvent(event); @@ -393,16 +423,15 @@ public class TouchscreenControllerView extends FrameLayout { return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics())); } - public void startLayoutEditing() { + public void startLayoutEditing(EditMode mode) { if (mEditLayout == null) { LayoutInflater inflater = LayoutInflater.from(getContext()); mEditLayout = (ConstraintLayout) inflater.inflate(R.layout.layout_touchscreen_controller_edit, this, false); - ((Button) mEditLayout.findViewById(R.id.stop_editing)).setOnClickListener((view) -> endLayoutEditing()); - ((Button) mEditLayout.findViewById(R.id.reset_layout)).setOnClickListener((view) -> clearTranslationForAllButtons()); + ((Button) mEditLayout.findViewById(R.id.options)).setOnClickListener((view) -> showEditorMenu()); addView(mEditLayout); } - mEditingLayout = true; + mEditMode = mode; } public void endLayoutEditing() { @@ -411,7 +440,7 @@ public class TouchscreenControllerView extends FrameLayout { mEditLayout = null; } - mEditingLayout = false; + mEditMode = EditMode.NONE; mMovingView = null; mMovingName = null; mMovingLastX = 0.0f; @@ -422,16 +451,26 @@ public class TouchscreenControllerView extends FrameLayout { AndroidHostInterface.getInstance().pauseEmulationThread(false); } + private float snapToValue(float pos, float value) { + return Math.round(pos / value) * value; + } + + private float snapToGrid(float pos) { + final float value = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20.0f, getResources().getDisplayMetrics()); + return snapToValue(pos, value); + } + private boolean handleEditingTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_UP: { if (mMovingView != null) { // save position - saveTranslationForButton(mMovingName, mMovingView.getTranslationX(), mMovingView.getTranslationY()); + saveSettingsForButton(mMovingName, mMovingView); mMovingView = null; mMovingName = null; mMovingLastX = 0.0f; mMovingLastY = 0.0f; + mMovingLastScale = 0.0f; } return true; @@ -451,8 +490,9 @@ public class TouchscreenControllerView extends FrameLayout { if (rect.contains((int) x, (int) y)) { mMovingView = buttonView; mMovingName = buttonView.getConfigName(); - mMovingLastX = x; - mMovingLastY = y; + mMovingLastX = snapToGrid(x); + mMovingLastY = snapToGrid(y); + mMovingLastScale = buttonView.getScaleX(); return true; } } @@ -462,8 +502,9 @@ public class TouchscreenControllerView extends FrameLayout { if (rect.contains((int) x, (int) y)) { mMovingView = axisView; mMovingName = axisView.getConfigName(); - mMovingLastX = x; - mMovingLastY = y; + mMovingLastX = snapToGrid(x); + mMovingLastY = snapToGrid(y); + mMovingLastScale = axisView.getScaleX(); return true; } } @@ -473,8 +514,9 @@ public class TouchscreenControllerView extends FrameLayout { if (rect.contains((int) x, (int) y)) { mMovingView = mDPadView; mMovingName = mDPadView.getConfigName(); - mMovingLastX = x; - mMovingLastY = y; + mMovingLastX = snapToGrid(x); + mMovingLastY = snapToGrid(y); + mMovingLastScale = mDPadView.getScaleX(); return true; } } @@ -487,19 +529,34 @@ public class TouchscreenControllerView extends FrameLayout { if (mMovingView == null) return true; - final float x = event.getX(); - final float y = event.getY(); - final float dx = x - mMovingLastX; - final float dy = y - mMovingLastY; - mMovingLastX = x; - mMovingLastY = y; + final float x = snapToGrid(event.getX()); + final float y = snapToGrid(event.getY()); + if (mEditMode == EditMode.POSITION) { + final float dx = x - mMovingLastX; + final float dy = y - mMovingLastY; + mMovingLastX = x; + mMovingLastY = y; + + final float posX = mMovingView.getX() + dx; + final float posY = mMovingView.getY() + dy; + //Log.d("Position", String.format("%f %f -> (%f %f) %f %f", + // mMovingView.getX(), mMovingView.getY(), dx, dy, posX, posY)); + mMovingView.setX(posX); + mMovingView.setY(posY); + } else { + final float lastDx = mMovingLastX - mMovingView.getX(); + final float lastDy = mMovingLastY - mMovingView.getY(); + final float dx = x - mMovingView.getX(); + final float dy = y - mMovingView.getY(); + final float lastDistance = Math.max(Math.abs(lastDx), Math.abs(lastDy)); + final float distance = Math.max(Math.abs(dx), Math.abs(dy)); + final float scaler = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50.0f, getResources().getDisplayMetrics()); + final float scaleDiff = snapToValue((distance - lastDistance) / scaler, 0.1f); + final float scale = Math.max(Math.min(mMovingLastScale + mMovingLastScale * scaleDiff, MAX_VIEW_SCALE), MIN_VIEW_SCALE); + mMovingView.setScaleX(scale); + mMovingView.setScaleY(scale); + } - final float posX = mMovingView.getX() + dx; - final float posY = mMovingView.getY() + dy; - //Log.d("Position", String.format("%f %f -> (%f %f) %f %f", - // mMovingView.getX(), mMovingView.getY(), dx, dy, posX, posY)); - mMovingView.setX(posX); - mMovingView.setY(posY); mMovingView.invalidate(); mMainView.requestLayout(); return true; @@ -561,7 +618,7 @@ public class TouchscreenControllerView extends FrameLayout { if ((rect.contains(x, y) && !axisView.isPressed()) || (axisView.isPressed() && axisView.getPointerId() == pointerId)) { - axisView.setPressed(pointerId, x, y); + axisView.setPressed(pointerId, x - rect.left, y - rect.top); pressed = true; mGlidePairs.put(pointerId, null); break; @@ -582,7 +639,7 @@ public class TouchscreenControllerView extends FrameLayout { final int x = (int) event.getX(i); final int y = (int) event.getY(i); if (rect.contains(x, y)) { - mDPadView.setPressed(event.getPointerId(i), x, y); + mDPadView.setPressed(event.getPointerId(i), x - rect.left, y - rect.top); pressed = true; } } @@ -712,4 +769,50 @@ public class TouchscreenControllerView extends FrameLayout { }); return builder; } + + private void showEditorMenu() { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setItems(R.array.touchscreen_layout_menu, (dialogInterface, i) -> { + switch (i) { + case 0: // Change Opacity + { + AlertDialog.Builder subBuilder = createOpacityDialog(getContext()); + subBuilder.create().show(); + } + break; + + case 1: // Add/Remove Buttons + { + AlertDialog.Builder subBuilder = createAddRemoveButtonDialog(getContext()); + subBuilder.create().show(); + } + break; + + case 2: // Edit Positions + { + mEditMode = EditMode.POSITION; + } + break; + + case 3: // Edit Scale + { + mEditMode = EditMode.SCALE; + } + break; + + case 4: // Reset Layout + { + clearTranslationForAllButtons(); + } + break; + + case 5: // Exit Editor + { + endLayoutEditing(); + } + break; + } + }); + builder.create().show(); + } } diff --git a/android/app/src/main/res/layout/layout_touchscreen_controller_edit.xml b/android/app/src/main/res/layout/layout_touchscreen_controller_edit.xml index 070b334bd..0dea1ecd9 100644 --- a/android/app/src/main/res/layout/layout_touchscreen_controller_edit.xml +++ b/android/app/src/main/res/layout/layout_touchscreen_controller_edit.xml @@ -6,22 +6,11 @@ android:layout_height="match_parent">