From d7c3a0137f5f9db34c1b64a6c601adaa718d5510 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Mon, 14 Dec 2020 00:56:04 +1000 Subject: [PATCH] Android: Support editing touchscreen controller layout --- android/.idea/gradle.xml | 1 + .../duckstation/EmulationActivity.java | 24 +- .../TouchscreenControllerAxisView.java | 11 +- .../TouchscreenControllerButtonView.java | 11 +- .../TouchscreenControllerView.java | 268 ++++++++++++++++-- .../layout_touchscreen_controller_edit.xml | 29 ++ android/app/src/main/res/values-it/arrays.xml | 3 +- android/app/src/main/res/values-nl/arrays.xml | 3 +- .../app/src/main/res/values-pt-rBR/arrays.xml | 3 +- android/app/src/main/res/values/arrays.xml | 3 +- android/app/src/main/res/values/strings.xml | 3 + 11 files changed, 325 insertions(+), 34 deletions(-) create mode 100644 android/app/src/main/res/layout/layout_touchscreen_controller_edit.xml diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml index 5cd135a06..9bba60dad 100644 --- a/android/.idea/gradle.xml +++ b/android/.idea/gradle.xml @@ -14,6 +14,7 @@ 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 8ed4a49d1..38a2e965b 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 @@ -296,6 +296,9 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_TOP_OR_LEFT); else AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_CENTER); + + if (mTouchscreenController != null) + mTouchscreenController.updateOrientation(); } private void enableFullscreenImmersive() { @@ -411,17 +414,28 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde return; } - case 3: // Change Touchscreen Controller + case 3: // Settings + { + Intent intent = new Intent(EmulationActivity.this, SettingsActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivityForResult(intent, REQUEST_CODE_SETTINGS); + return; + } + + case 4: // Change Touchscreen Controller { showTouchscreenControllerMenu(); return; } - case 4: // Settings + case 5: // Edit Touchscreen Controller Layout { - Intent intent = new Intent(EmulationActivity.this, SettingsActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResult(intent, REQUEST_CODE_SETTINGS); + if (mTouchscreenController != null) { + mTouchscreenController.startLayoutEditing(); + } else { + Toast.makeText(this, R.string.emulation_activity_touchscreen_controller_not_active, Toast.LENGTH_SHORT); + } + return; } } 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 70462031e..44436dfd3 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 @@ -6,7 +6,7 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; -public class TouchscreenControllerAxisView extends View { +public final class TouchscreenControllerAxisView extends View { private Drawable mBaseDrawable; private Drawable mStickUnpressedDrawable; private Drawable mStickPressedDrawable; @@ -17,6 +17,7 @@ public class TouchscreenControllerAxisView extends View { private int mDrawXPos = 0; private int mDrawYPos = 0; + private String mConfigName; private int mControllerIndex = -1; private int mXAxisCode = -1; private int mYAxisCode = -1; @@ -49,6 +50,14 @@ public class TouchscreenControllerAxisView extends View { mStickPressedDrawable.setCallback(this); } + public String getConfigName() { + return mConfigName; + } + + public void setConfigName(String configName) { + mConfigName = configName; + } + public void setControllerAxis(int controllerIndex, int xCode, int yCode) { mControllerIndex = controllerIndex; mXAxisCode = xCode; 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 ec273f921..373e8a8ae 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 @@ -11,13 +11,14 @@ import android.view.View; /** * TODO: document your custom view class. */ -public class TouchscreenControllerButtonView extends View { +public final class TouchscreenControllerButtonView extends View { private Drawable mUnpressedDrawable; private Drawable mPressedDrawable; private boolean mPressed = false; private boolean mHapticFeedback = false; private int mControllerIndex = -1; private int mButtonCode = -1; + private String mConfigName; public TouchscreenControllerButtonView(Context context) { super(context); @@ -94,6 +95,14 @@ public class TouchscreenControllerButtonView extends View { mButtonCode = code; } + public void setConfigName(String name) { + mConfigName = name; + } + + public String getConfigName() { + return mConfigName; + } + public void setHapticFeedback(boolean enabled) { mHapticFeedback = enabled; } 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 c62774fd7..19b7961f0 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 @@ -1,13 +1,22 @@ package com.github.stenzek.duckstation; import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; import android.widget.FrameLayout; +import android.widget.RelativeLayout; + +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.preference.PreferenceManager; import java.util.ArrayList; @@ -17,10 +26,18 @@ import java.util.ArrayList; public class TouchscreenControllerView extends FrameLayout { private int mControllerIndex; private String mControllerType; + private String mViewType; private View mMainView; private ArrayList mButtonViews = new ArrayList<>(); private ArrayList mAxisViews = new ArrayList<>(); private boolean mHapticFeedback; + private String mLayoutOrientation; + private boolean mEditingLayout = false; + private View mMovingView = null; + private String mMovingName = null; + private float mMovingLastX = 0.0f; + private float mMovingLastY = 0.0f; + private ConstraintLayout mEditLayout = null; public TouchscreenControllerView(Context context) { super(context); @@ -34,10 +51,101 @@ public class TouchscreenControllerView extends FrameLayout { super(context, attrs, defStyle); } + private String getConfigKeyForXTranslation(String name) { + return String.format("TouchscreenController/%s/%s%sXTranslation", mViewType, name, mLayoutOrientation); + } + + private String getConfigKeyForYTranslation(String name) { + return String.format("TouchscreenController/%s/%s%sYTranslation", mViewType, name, mLayoutOrientation); + } + + private void saveTranslationForButton(String name, float xTranslation, float yTranslation) { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + final SharedPreferences.Editor editor = prefs.edit(); + editor.putFloat(getConfigKeyForXTranslation(name), xTranslation); + editor.putFloat(getConfigKeyForYTranslation(name), yTranslation); + editor.commit(); + } + + private void clearTranslationForAllButtons() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + final SharedPreferences.Editor editor = prefs.edit(); + + for (TouchscreenControllerButtonView buttonView : mButtonViews) { + editor.remove(getConfigKeyForXTranslation(buttonView.getConfigName())); + editor.remove(getConfigKeyForYTranslation(buttonView.getConfigName())); + buttonView.setTranslationX(0.0f); + buttonView.setTranslationY(0.0f); + } + + for (TouchscreenControllerAxisView axisView : mAxisViews) { + editor.remove(getConfigKeyForXTranslation(axisView.getConfigName())); + editor.remove(getConfigKeyForYTranslation(axisView.getConfigName())); + axisView.setTranslationX(0.0f); + axisView.setTranslationY(0.0f); + } + + editor.commit(); + requestLayout(); + } + + private void reloadButtonTranslation() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + + for (TouchscreenControllerButtonView buttonView : mButtonViews) { + try { + buttonView.setTranslationX(prefs.getFloat(getConfigKeyForXTranslation(buttonView.getConfigName()), 0.0f)); + buttonView.setTranslationY(prefs.getFloat(getConfigKeyForYTranslation(buttonView.getConfigName()), 0.0f)); + //Log.i("TouchscreenController", String.format("Translation for %s %f %f", buttonView.getConfigName(), + // buttonView.getTranslationX(), buttonView.getTranslationY())); + } catch (ClassCastException ex) { + + } + } + + for (TouchscreenControllerAxisView axisView : mAxisViews) { + try { + axisView.setTranslationX(prefs.getFloat(getConfigKeyForXTranslation(axisView.getConfigName()), 0.0f)); + axisView.setTranslationY(prefs.getFloat(getConfigKeyForYTranslation(axisView.getConfigName()), 0.0f)); + } catch (ClassCastException ex) { + + } + } + } + + private String getOrientationString() { + switch (getContext().getResources().getConfiguration().orientation) { + case Configuration.ORIENTATION_PORTRAIT: + return "Portrait"; + case Configuration.ORIENTATION_LANDSCAPE: + default: + return "Landscape"; + } + } + + /** + * Checks if the orientation of the layout has changed, and if so, reloads button translations. + */ + public void updateOrientation() { + String newOrientation = getOrientationString(); + if (mLayoutOrientation != null && mLayoutOrientation.equals(newOrientation)) + return; + + Log.i("TouchscreenController", "New orientation: " + newOrientation); + mLayoutOrientation = newOrientation; + reloadButtonTranslation(); + requestLayout(); + } + public void init(int controllerIndex, String controllerType, String viewType, boolean hapticFeedback) { mControllerIndex = controllerIndex; mControllerType = controllerType; + mViewType = viewType; mHapticFeedback = hapticFeedback; + mLayoutOrientation = getOrientationString(); + + if (mEditingLayout) + endLayoutEditing(); mButtonViews.clear(); mAxisViews.clear(); @@ -67,31 +175,38 @@ public class TouchscreenControllerView extends FrameLayout { return; mMainView.setOnTouchListener((view1, event) -> { - return handleTouchEvent(event); + if (mEditingLayout) + return handleEditingTouchEvent(event); + else + return handleTouchEvent(event); }); - 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"); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + + linkButton(mMainView, R.id.controller_button_up, "UpButton", "Up"); + linkButton(mMainView, R.id.controller_button_right, "RightButton", "Right"); + linkButton(mMainView, R.id.controller_button_down, "DownButton", "Down"); + linkButton(mMainView, R.id.controller_button_left, "LeftButton", "Left"); + linkButton(mMainView, R.id.controller_button_l1, "L1Button", "L1"); + linkButton(mMainView, R.id.controller_button_l2, "L2Button", "L2"); + linkButton(mMainView, R.id.controller_button_select, "SelectButton", "Select"); + linkButton(mMainView, R.id.controller_button_start, "StartButton", "Start"); + linkButton(mMainView, R.id.controller_button_triangle, "TriangleButton", "Triangle"); + linkButton(mMainView, R.id.controller_button_circle, "CircleButton", "Circle"); + linkButton(mMainView, R.id.controller_button_cross, "CrossButton", "Cross"); + linkButton(mMainView, R.id.controller_button_square, "SquareButton", "Square"); + linkButton(mMainView, R.id.controller_button_r1, "R1Button", "R1"); + linkButton(mMainView, R.id.controller_button_r2, "R2Button", "R2"); + + if (!linkAxis(mMainView, R.id.controller_axis_left, "LeftAxis", "Left")) + linkAxisToButtons(mMainView, R.id.controller_axis_left, "LeftAxis", ""); + + linkAxis(mMainView, R.id.controller_axis_right, "RightAxis", "Right"); + reloadButtonTranslation(); + requestLayout(); } - private void linkButton(View view, int id, String buttonName) { + private void linkButton(View view, int id, String configName, String buttonName) { TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id); if (buttonView == null) return; @@ -100,6 +215,7 @@ public class TouchscreenControllerView extends FrameLayout { Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code)); if (code >= 0) { + buttonView.setConfigName(configName); buttonView.setButtonCode(mControllerIndex, code); buttonView.setHapticFeedback(mHapticFeedback); mButtonViews.add(buttonView); @@ -109,7 +225,7 @@ public class TouchscreenControllerView extends FrameLayout { } } - private boolean linkAxis(View view, int id, String axisName) { + private boolean linkAxis(View view, int id, String configName, String axisName) { TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id); if (axisView == null) return false; @@ -120,12 +236,13 @@ public class TouchscreenControllerView extends FrameLayout { if (xCode < 0 && yCode < 0) return false; + axisView.setConfigName(configName); axisView.setControllerAxis(mControllerIndex, xCode, yCode); mAxisViews.add(axisView); return true; } - private boolean linkAxisToButtons(View view, int id, String buttonPrefix) { + private boolean linkAxisToButtons(View view, int id, String configName, String buttonPrefix) { TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id); if (axisView == null) return false; @@ -143,6 +260,111 @@ public class TouchscreenControllerView extends FrameLayout { return true; } + private int dpToPixels(float dp) { + return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics())); + } + + public void startLayoutEditing() { + 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()); + addView(mEditLayout); + } + + mEditingLayout = true; + } + + public void endLayoutEditing() { + if (mEditLayout != null) { + ((ViewGroup) mMainView).removeView(mEditLayout); + mEditLayout = null; + } + + mEditingLayout = false; + mMovingView = null; + mMovingName = null; + mMovingLastX = 0.0f; + mMovingLastY = 0.0f; + } + + private boolean handleEditingTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_UP: { + if (mMovingView != null) { + // save position + saveTranslationForButton(mMovingName, mMovingView.getTranslationX(), mMovingView.getTranslationY()); + mMovingView = null; + mMovingName = null; + mMovingLastX = 0.0f; + mMovingLastY = 0.0f; + } + + return true; + } + + case MotionEvent.ACTION_DOWN: { + if (mMovingView != null) { + // already moving a button + return true; + } + + Rect rect = new Rect(); + final float x = event.getX(); + final float y = event.getY(); + for (TouchscreenControllerButtonView buttonView : mButtonViews) { + buttonView.getHitRect(rect); + if (rect.contains((int) x, (int) y)) { + mMovingView = buttonView; + mMovingName = buttonView.getConfigName(); + mMovingLastX = x; + mMovingLastY = y; + return true; + } + } + + for (TouchscreenControllerAxisView axisView : mAxisViews) { + axisView.getHitRect(rect); + if (rect.contains((int) x, (int) y)) { + mMovingView = axisView; + mMovingName = axisView.getConfigName(); + mMovingLastX = x; + mMovingLastY = y; + return true; + } + } + + // nothing.. + return true; + } + + case MotionEvent.ACTION_MOVE: { + 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 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; + } + } + + return false; + } + private boolean handleTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_UP: { 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 new file mode 100644 index 000000000..070b334bd --- /dev/null +++ b/android/app/src/main/res/layout/layout_touchscreen_controller_edit.xml @@ -0,0 +1,29 @@ + + + +