Android: Support editing touchscreen controller layout

This commit is contained in:
Connor McLaughlin 2020-12-14 00:56:04 +10:00
parent 76c945a6e3
commit d7c3a0137f
11 changed files with 325 additions and 34 deletions

View file

@ -14,6 +14,7 @@
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View file

@ -296,6 +296,9 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_TOP_OR_LEFT); AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_TOP_OR_LEFT);
else else
AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_CENTER); AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_CENTER);
if (mTouchscreenController != null)
mTouchscreenController.updateOrientation();
} }
private void enableFullscreenImmersive() { private void enableFullscreenImmersive() {
@ -411,17 +414,28 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
return; 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(); showTouchscreenControllerMenu();
return; return;
} }
case 4: // Settings case 5: // Edit Touchscreen Controller Layout
{ {
Intent intent = new Intent(EmulationActivity.this, SettingsActivity.class); if (mTouchscreenController != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mTouchscreenController.startLayoutEditing();
startActivityForResult(intent, REQUEST_CODE_SETTINGS); } else {
Toast.makeText(this, R.string.emulation_activity_touchscreen_controller_not_active, Toast.LENGTH_SHORT);
}
return; return;
} }
} }

View file

@ -6,7 +6,7 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
public class TouchscreenControllerAxisView extends View { public final class TouchscreenControllerAxisView extends View {
private Drawable mBaseDrawable; private Drawable mBaseDrawable;
private Drawable mStickUnpressedDrawable; private Drawable mStickUnpressedDrawable;
private Drawable mStickPressedDrawable; private Drawable mStickPressedDrawable;
@ -17,6 +17,7 @@ public class TouchscreenControllerAxisView extends View {
private int mDrawXPos = 0; private int mDrawXPos = 0;
private int mDrawYPos = 0; private int mDrawYPos = 0;
private String mConfigName;
private int mControllerIndex = -1; private int mControllerIndex = -1;
private int mXAxisCode = -1; private int mXAxisCode = -1;
private int mYAxisCode = -1; private int mYAxisCode = -1;
@ -49,6 +50,14 @@ public class TouchscreenControllerAxisView extends View {
mStickPressedDrawable.setCallback(this); mStickPressedDrawable.setCallback(this);
} }
public String getConfigName() {
return mConfigName;
}
public void setConfigName(String configName) {
mConfigName = configName;
}
public void setControllerAxis(int controllerIndex, int xCode, int yCode) { public void setControllerAxis(int controllerIndex, int xCode, int yCode) {
mControllerIndex = controllerIndex; mControllerIndex = controllerIndex;
mXAxisCode = xCode; mXAxisCode = xCode;

View file

@ -11,13 +11,14 @@ import android.view.View;
/** /**
* TODO: document your custom view class. * TODO: document your custom view class.
*/ */
public class TouchscreenControllerButtonView extends View { public final class TouchscreenControllerButtonView extends View {
private Drawable mUnpressedDrawable; private Drawable mUnpressedDrawable;
private Drawable mPressedDrawable; private Drawable mPressedDrawable;
private boolean mPressed = false; private boolean mPressed = false;
private boolean mHapticFeedback = false; private boolean mHapticFeedback = false;
private int mControllerIndex = -1; private int mControllerIndex = -1;
private int mButtonCode = -1; private int mButtonCode = -1;
private String mConfigName;
public TouchscreenControllerButtonView(Context context) { public TouchscreenControllerButtonView(Context context) {
super(context); super(context);
@ -94,6 +95,14 @@ public class TouchscreenControllerButtonView extends View {
mButtonCode = code; mButtonCode = code;
} }
public void setConfigName(String name) {
mConfigName = name;
}
public String getConfigName() {
return mConfigName;
}
public void setHapticFeedback(boolean enabled) { public void setHapticFeedback(boolean enabled) {
mHapticFeedback = enabled; mHapticFeedback = enabled;
} }

View file

@ -1,13 +1,22 @@
package com.github.stenzek.duckstation; package com.github.stenzek.duckstation;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.preference.PreferenceManager;
import java.util.ArrayList; import java.util.ArrayList;
@ -17,10 +26,18 @@ import java.util.ArrayList;
public class TouchscreenControllerView extends FrameLayout { public class TouchscreenControllerView extends FrameLayout {
private int mControllerIndex; private int mControllerIndex;
private String mControllerType; private String mControllerType;
private String mViewType;
private View mMainView; private View mMainView;
private ArrayList<TouchscreenControllerButtonView> mButtonViews = new ArrayList<>(); private ArrayList<TouchscreenControllerButtonView> mButtonViews = new ArrayList<>();
private ArrayList<TouchscreenControllerAxisView> mAxisViews = new ArrayList<>(); private ArrayList<TouchscreenControllerAxisView> mAxisViews = new ArrayList<>();
private boolean mHapticFeedback; 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) { public TouchscreenControllerView(Context context) {
super(context); super(context);
@ -34,10 +51,101 @@ public class TouchscreenControllerView extends FrameLayout {
super(context, attrs, defStyle); 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) { public void init(int controllerIndex, String controllerType, String viewType, boolean hapticFeedback) {
mControllerIndex = controllerIndex; mControllerIndex = controllerIndex;
mControllerType = controllerType; mControllerType = controllerType;
mViewType = viewType;
mHapticFeedback = hapticFeedback; mHapticFeedback = hapticFeedback;
mLayoutOrientation = getOrientationString();
if (mEditingLayout)
endLayoutEditing();
mButtonViews.clear(); mButtonViews.clear();
mAxisViews.clear(); mAxisViews.clear();
@ -67,31 +175,38 @@ public class TouchscreenControllerView extends FrameLayout {
return; return;
mMainView.setOnTouchListener((view1, event) -> { mMainView.setOnTouchListener((view1, event) -> {
return handleTouchEvent(event); if (mEditingLayout)
return handleEditingTouchEvent(event);
else
return handleTouchEvent(event);
}); });
linkButton(mMainView, R.id.controller_button_up, "Up"); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
linkButton(mMainView, R.id.controller_button_right, "Right");
linkButton(mMainView, R.id.controller_button_down, "Down"); linkButton(mMainView, R.id.controller_button_up, "UpButton", "Up");
linkButton(mMainView, R.id.controller_button_left, "Left"); linkButton(mMainView, R.id.controller_button_right, "RightButton", "Right");
linkButton(mMainView, R.id.controller_button_l1, "L1"); linkButton(mMainView, R.id.controller_button_down, "DownButton", "Down");
linkButton(mMainView, R.id.controller_button_l2, "L2"); linkButton(mMainView, R.id.controller_button_left, "LeftButton", "Left");
linkButton(mMainView, R.id.controller_button_select, "Select"); linkButton(mMainView, R.id.controller_button_l1, "L1Button", "L1");
linkButton(mMainView, R.id.controller_button_start, "Start"); linkButton(mMainView, R.id.controller_button_l2, "L2Button", "L2");
linkButton(mMainView, R.id.controller_button_triangle, "Triangle"); linkButton(mMainView, R.id.controller_button_select, "SelectButton", "Select");
linkButton(mMainView, R.id.controller_button_circle, "Circle"); linkButton(mMainView, R.id.controller_button_start, "StartButton", "Start");
linkButton(mMainView, R.id.controller_button_cross, "Cross"); linkButton(mMainView, R.id.controller_button_triangle, "TriangleButton", "Triangle");
linkButton(mMainView, R.id.controller_button_square, "Square"); linkButton(mMainView, R.id.controller_button_circle, "CircleButton", "Circle");
linkButton(mMainView, R.id.controller_button_r1, "R1"); linkButton(mMainView, R.id.controller_button_cross, "CrossButton", "Cross");
linkButton(mMainView, R.id.controller_button_r2, "R2"); linkButton(mMainView, R.id.controller_button_square, "SquareButton", "Square");
linkButton(mMainView, R.id.controller_button_r1, "R1Button", "R1");
if (!linkAxis(mMainView, R.id.controller_axis_left, "Left")) linkButton(mMainView, R.id.controller_button_r2, "R2Button", "R2");
linkAxisToButtons(mMainView, R.id.controller_axis_left, "");
if (!linkAxis(mMainView, R.id.controller_axis_left, "LeftAxis", "Left"))
linkAxis(mMainView, R.id.controller_axis_right, "Right"); 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); TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id);
if (buttonView == null) if (buttonView == null)
return; return;
@ -100,6 +215,7 @@ public class TouchscreenControllerView extends FrameLayout {
Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code)); Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code));
if (code >= 0) { if (code >= 0) {
buttonView.setConfigName(configName);
buttonView.setButtonCode(mControllerIndex, code); buttonView.setButtonCode(mControllerIndex, code);
buttonView.setHapticFeedback(mHapticFeedback); buttonView.setHapticFeedback(mHapticFeedback);
mButtonViews.add(buttonView); 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); TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id);
if (axisView == null) if (axisView == null)
return false; return false;
@ -120,12 +236,13 @@ public class TouchscreenControllerView extends FrameLayout {
if (xCode < 0 && yCode < 0) if (xCode < 0 && yCode < 0)
return false; return false;
axisView.setConfigName(configName);
axisView.setControllerAxis(mControllerIndex, xCode, yCode); axisView.setControllerAxis(mControllerIndex, xCode, yCode);
mAxisViews.add(axisView); mAxisViews.add(axisView);
return true; 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); TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id);
if (axisView == null) if (axisView == null)
return false; return false;
@ -143,6 +260,111 @@ public class TouchscreenControllerView extends FrameLayout {
return true; 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) { private boolean handleTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) { switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP: { case MotionEvent.ACTION_UP: {

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/stop_editing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/touchscreen_controller_stop_editing"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/reset_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="64dp"
android:text="@string/touchscreen_controller_reset_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -87,8 +87,9 @@
<item>Reset</item> <item>Reset</item>
<item>Codici Patch</item> <item>Codici Patch</item>
<item>Cambia Disco</item> <item>Cambia Disco</item>
<item>Cambia Controller Touchscreen</item>
<item>Impostazioni</item> <item>Impostazioni</item>
<item>Cambia Controller Touchscreen</item>
<item>Edit Touchscreen Controller Layout</item>
</string-array> </string-array>
<string-array name="emulation_save_state_slot_menu"> <string-array name="emulation_save_state_slot_menu">
<item>Slot Veloce</item> <item>Slot Veloce</item>

View file

@ -87,8 +87,9 @@
<item>Resetten</item> <item>Resetten</item>
<item>Patch Codes</item> <item>Patch Codes</item>
<item>Disc Veranderen</item> <item>Disc Veranderen</item>
<item>Touchscreen Controller Aanpassen</item>
<item>Instellingen</item> <item>Instellingen</item>
<item>Touchscreen Controller Aanpassen</item>
<item>Edit Touchscreen Controller Layout</item>
</string-array> </string-array>
<string-array name="emulation_save_state_slot_menu"> <string-array name="emulation_save_state_slot_menu">
<item>Snel Slot</item> <item>Snel Slot</item>

View file

@ -87,8 +87,9 @@
<item>Reiniciar</item> <item>Reiniciar</item>
<item>Trapaças</item> <item>Trapaças</item>
<item>Mudar Disco</item> <item>Mudar Disco</item>
<item>Mudar controle em Tela</item>
<item>Configurações</item> <item>Configurações</item>
<item>Mudar controle em Tela</item>
<item>Edit Touchscreen Controller Layout</item>
</string-array> </string-array>
<string-array name="emulation_save_state_slot_menu"> <string-array name="emulation_save_state_slot_menu">
<item>Armazenamento Rápido</item> <item>Armazenamento Rápido</item>

View file

@ -163,8 +163,9 @@
<item>Reset</item> <item>Reset</item>
<item>Patch Codes</item> <item>Patch Codes</item>
<item>Change Disc</item> <item>Change Disc</item>
<item>Change Touchscreen Controller</item>
<item>Settings</item> <item>Settings</item>
<item>Change Touchscreen Controller</item>
<item>Edit Touchscreen Controller Layout</item>
</string-array> </string-array>
<string-array name="emulation_save_state_slot_menu"> <string-array name="emulation_save_state_slot_menu">
<item>Quick Slot</item> <item>Quick Slot</item>

View file

@ -152,4 +152,7 @@
<string name="settings_gpu_thread">Threaded GPU Rendering</string> <string name="settings_gpu_thread">Threaded GPU Rendering</string>
<string name="settings_summary_gpu_thread">Uses a second thread for drawing graphics. Currently only available for the software renderer, but can provide a significant speed improvement, and is safe to use.</string> <string name="settings_summary_gpu_thread">Uses a second thread for drawing graphics. Currently only available for the software renderer, but can provide a significant speed improvement, and is safe to use.</string>
<string name="settings_language">Language (restart to apply)</string> <string name="settings_language">Language (restart to apply)</string>
<string name="touchscreen_controller_stop_editing">Stop Editing</string>
<string name="touchscreen_controller_reset_layout">Reset Layout</string>
<string name="emulation_activity_touchscreen_controller_not_active">Touchscreen controller is not active.</string>
</resources> </resources>