Android: Implement controller auto-fire

This commit is contained in:
Connor McLaughlin 2021-05-24 21:18:31 +10:00
parent 8f23a79d36
commit 350bae7e94
19 changed files with 504 additions and 16 deletions

View file

@ -465,7 +465,6 @@ void AndroidHostInterface::EmulationThreadEntryPoint(JNIEnv* env, jobject emulat
emulation_activity = env->NewGlobalRef(emulation_activity); emulation_activity = env->NewGlobalRef(emulation_activity);
Assert(emulation_activity != nullptr); Assert(emulation_activity != nullptr);
{ {
std::unique_lock<std::mutex> lock(m_mutex); std::unique_lock<std::mutex> lock(m_mutex);
m_emulation_thread_running.store(true); m_emulation_thread_running.store(true);
@ -718,7 +717,8 @@ void AndroidHostInterface::OnRunningGameChanged(const std::string& path, CDImage
if (!cover_path_str.empty()) if (!cover_path_str.empty())
cover_path = env->NewStringUTF(cover_path_str.c_str()); cover_path = env->NewStringUTF(cover_path_str.c_str());
env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onRunningGameChanged, path_string, code_string, title_string, cover_path); env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onRunningGameChanged, path_string,
code_string, title_string, cover_path);
if (cover_path) if (cover_path)
env->DeleteLocalRef(cover_path); env->DeleteLocalRef(cover_path);
@ -998,7 +998,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
(s_AndroidHostInterface_field_mNativePointer = (s_AndroidHostInterface_field_mNativePointer =
env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr || env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr ||
(s_AndroidHostInterface_field_mEmulationActivity = (s_AndroidHostInterface_field_mEmulationActivity =
env->GetFieldID(s_AndroidHostInterface_class, "mEmulationActivity", "Lcom/github/stenzek/duckstation/EmulationActivity;")) == nullptr || env->GetFieldID(s_AndroidHostInterface_class, "mEmulationActivity",
"Lcom/github/stenzek/duckstation/EmulationActivity;")) == nullptr ||
(s_AndroidHostInterface_method_reportError = (s_AndroidHostInterface_method_reportError =
env->GetMethodID(s_AndroidHostInterface_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || env->GetMethodID(s_AndroidHostInterface_class, "reportError", "(Ljava/lang/String;)V")) == nullptr ||
(s_AndroidHostInterface_method_reportMessage = (s_AndroidHostInterface_method_reportMessage =
@ -1014,7 +1015,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
(s_EmulationActivity_method_onEmulationStopped = (s_EmulationActivity_method_onEmulationStopped =
env->GetMethodID(s_EmulationActivity_class, "onEmulationStopped", "()V")) == nullptr || env->GetMethodID(s_EmulationActivity_class, "onEmulationStopped", "()V")) == nullptr ||
(s_EmulationActivity_method_onRunningGameChanged = (s_EmulationActivity_method_onRunningGameChanged =
env->GetMethodID(s_EmulationActivity_class, "onRunningGameChanged", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V")) == nullptr || env->GetMethodID(s_EmulationActivity_class, "onRunningGameChanged",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V")) == nullptr ||
(s_EmulationActivity_method_setVibration = env->GetMethodID(emulation_activity_class, "setVibration", "(Z)V")) == (s_EmulationActivity_method_setVibration = env->GetMethodID(emulation_activity_class, "setVibration", "(Z)V")) ==
nullptr || nullptr ||
(s_EmulationActivity_method_getRefreshRate = (s_EmulationActivity_method_getRefreshRate =
@ -1187,6 +1189,18 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerButtonState, jobj
AndroidHelpers::GetNativeClass(env, obj)->SetControllerButtonState(index, button_code, pressed); AndroidHelpers::GetNativeClass(env, obj)->SetControllerButtonState(index, button_code, pressed);
} }
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerAutoFireState, jobject obj, jint controller_index,
jint autofire_index, jboolean active)
{
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
if (!hi->IsEmulationThreadRunning())
return;
hi->RunOnEmulationThread([hi, controller_index, autofire_index, active]() {
hi->SetControllerAutoFireSlotState(controller_index, autofire_index, active);
});
}
DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerButtonCode, jobject unused, jstring controller_type, DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerButtonCode, jobject unused, jstring controller_type,
jstring button_name) jstring button_name)
{ {

View file

@ -79,6 +79,8 @@ public class AndroidHostInterface {
public native void setControllerAxisState(int index, int axisCode, float value); public native void setControllerAxisState(int index, int axisCode, float value);
public native void setControllerAutoFireState(int controllerIndex, int autoFireIndex, boolean active);
public native void setMousePosition(int positionX, int positionY); public native void setMousePosition(int positionX, int positionY);
public static native int getControllerButtonCode(String controllerType, String buttonName); public static native int getControllerButtonCode(String controllerType, String buttonName);

View file

@ -177,6 +177,15 @@ public class ControllerBindingPreference extends Preference {
updateValue(); updateValue();
} }
public void initAutoFireButton(int controllerIndex, int autoFireSlot) {
mBindingName = String.format("AutoFire%d", autoFireSlot);
mDisplayName = getContext().getString(R.string.controller_binding_auto_fire_n, autoFireSlot);
mType = Type.BUTTON;
mVisualType = VisualType.BUTTON;
setKey(String.format("Controller%d/AutoFire%d", controllerIndex, autoFireSlot));
updateValue();
}
private String prettyPrintBinding(String value) { private String prettyPrintBinding(String value) {
final int index = value.indexOf('/'); final int index = value.indexOf('/');
String device, binding; String device, binding;

View file

@ -1,16 +1,20 @@
package com.github.stenzek.duckstation; package com.github.stenzek.duckstation;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
@ -18,6 +22,7 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreferenceCompat; import androidx.preference.SwitchPreferenceCompat;
import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2; import androidx.viewpager2.widget.ViewPager2;
@ -33,6 +38,7 @@ public class ControllerSettingsCollectionFragment extends Fragment {
private static final int NUM_MAIN_CONTROLLER_PORTS = 2; private static final int NUM_MAIN_CONTROLLER_PORTS = 2;
private static final int NUM_SUB_CONTROLLER_PORTS = 4; private static final int NUM_SUB_CONTROLLER_PORTS = 4;
private static final char[] SUB_CONTROLLER_PORT_NAMES = new char[]{'A', 'B', 'C', 'D'}; private static final char[] SUB_CONTROLLER_PORT_NAMES = new char[]{'A', 'B', 'C', 'D'};
private static final int NUM_AUTO_FIRE_BUTTONS = 4;
public interface MultitapModeChangedListener { public interface MultitapModeChangedListener {
void onChanged(); void onChanged();
@ -150,6 +156,8 @@ public class ControllerSettingsCollectionFragment extends Fragment {
private PreferenceCategory mButtonsCategory; private PreferenceCategory mButtonsCategory;
private PreferenceCategory mAxisCategory; private PreferenceCategory mAxisCategory;
private PreferenceCategory mSettingsCategory; private PreferenceCategory mSettingsCategory;
private PreferenceCategory mAutoFireCategory;
private PreferenceCategory mAutoFireBindingsCategory;
public ControllerPortFragment(ControllerSettingsCollectionFragment parent, int controllerIndex) { public ControllerPortFragment(ControllerSettingsCollectionFragment parent, int controllerIndex) {
this.parent = parent; this.parent = parent;
@ -181,6 +189,14 @@ public class ControllerSettingsCollectionFragment extends Fragment {
return pref; return pref;
} }
private String getAutoToggleSummary(SharedPreferences sp, int slot) {
final String button = sp.getString(String.format("AutoFire%dButton", slot), null);
if (button == null || button.length() == 0)
return "Not Configured";
return String.format("%s every %d frames", button, sp.getInt("AutoFire%dFrequency", 2));
}
private void createPreferences() { private void createPreferences() {
final PreferenceScreen ps = getPreferenceScreen(); final PreferenceScreen ps = getPreferenceScreen();
final SharedPreferences sp = getPreferenceManager().getSharedPreferences(); final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
@ -236,23 +252,34 @@ public class ControllerSettingsCollectionFragment extends Fragment {
ps.addPreference(clearBindingsPreference); ps.addPreference(clearBindingsPreference);
mButtonsCategory = new PreferenceCategory(getContext()); mButtonsCategory = new PreferenceCategory(getContext());
mButtonsCategory.setTitle(getContext().getString(R.string.controller_settings_category_button_bindings)); mButtonsCategory.setTitle(R.string.controller_settings_category_button_bindings);
mButtonsCategory.setIconSpaceReserved(false); mButtonsCategory.setIconSpaceReserved(false);
ps.addPreference(mButtonsCategory); ps.addPreference(mButtonsCategory);
mAxisCategory = new PreferenceCategory(getContext()); mAxisCategory = new PreferenceCategory(getContext());
mAxisCategory.setTitle(getContext().getString(R.string.controller_settings_category_axis_bindings)); mAxisCategory.setTitle(R.string.controller_settings_category_axis_bindings);
mAxisCategory.setIconSpaceReserved(false); mAxisCategory.setIconSpaceReserved(false);
ps.addPreference(mAxisCategory); ps.addPreference(mAxisCategory);
mSettingsCategory = new PreferenceCategory(getContext()); mSettingsCategory = new PreferenceCategory(getContext());
mSettingsCategory.setTitle(getContext().getString(R.string.controller_settings_category_settings)); mSettingsCategory.setTitle(R.string.controller_settings_category_settings);
mSettingsCategory.setIconSpaceReserved(false); mSettingsCategory.setIconSpaceReserved(false);
ps.addPreference(mSettingsCategory); ps.addPreference(mSettingsCategory);
mAutoFireCategory = new PreferenceCategory(getContext());
mAutoFireCategory.setTitle(R.string.controller_settings_category_auto_fire_buttons);
mAutoFireCategory.setIconSpaceReserved(false);
ps.addPreference(mAutoFireCategory);
mAutoFireBindingsCategory = new PreferenceCategory(getContext());
mAutoFireBindingsCategory.setTitle(R.string.controller_settings_category_auto_fire_bindings);
mAutoFireBindingsCategory.setIconSpaceReserved(false);
ps.addPreference(mAutoFireBindingsCategory);
createPreferences(controllerType); createPreferences(controllerType);
} }
@SuppressLint("DefaultLocale")
private void createPreferences(String controllerType) { private void createPreferences(String controllerType) {
final PreferenceScreen ps = getPreferenceScreen(); final PreferenceScreen ps = getPreferenceScreen();
final SharedPreferences sp = getPreferenceManager().getSharedPreferences(); final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
@ -295,6 +322,33 @@ public class ControllerSettingsCollectionFragment extends Fragment {
createTogglePreference(String.format("Controller%d/AnalogDPadInDigitalMode", controllerIndex), createTogglePreference(String.format("Controller%d/AnalogDPadInDigitalMode", controllerIndex),
R.string.settings_use_analog_sticks_for_dpad, R.string.settings_summary_use_analog_sticks_for_dpad, true)); R.string.settings_use_analog_sticks_for_dpad, R.string.settings_summary_use_analog_sticks_for_dpad, true));
} }
for (int autoFireSlot = 1; autoFireSlot <= NUM_AUTO_FIRE_BUTTONS; autoFireSlot++) {
final ListPreference autoFirePreference = new ListPreference(getContext());
autoFirePreference.setEntries(buttonNames);
autoFirePreference.setEntryValues(buttonNames);
autoFirePreference.setKey(String.format("Controller%d/AutoFire%dButton", controllerIndex, autoFireSlot));
autoFirePreference.setTitle(getContext().getString(R.string.controller_settings_auto_fire_n_button, autoFireSlot));
autoFirePreference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
autoFirePreference.setIconSpaceReserved(false);
mAutoFireCategory.addPreference(autoFirePreference);
final SeekBarPreference frequencyPreference = new SeekBarPreference(getContext());
frequencyPreference.setMin(1);
frequencyPreference.setMax(60);
frequencyPreference.setKey(String.format("Controller%d/AutoFire%dFrequency", controllerIndex, autoFireSlot));
frequencyPreference.setDefaultValue(2);
frequencyPreference.setTitle(getContext().getString(R.string.controller_settings_auto_fire_n_frequency, autoFireSlot));
frequencyPreference.setIconSpaceReserved(false);
frequencyPreference.setShowSeekBarValue(true);
mAutoFireCategory.addPreference(frequencyPreference);
}
for (int autoFireSlot = 1; autoFireSlot <= NUM_AUTO_FIRE_BUTTONS; autoFireSlot++) {
final ControllerBindingPreference bindingPreference = new ControllerBindingPreference(getContext(), null);
bindingPreference.initAutoFireButton(controllerIndex, autoFireSlot);
mAutoFireBindingsCategory.addPreference(bindingPreference);
}
} }
private void removePreferences() { private void removePreferences() {
@ -312,6 +366,16 @@ public class ControllerSettingsCollectionFragment extends Fragment {
parent.preferences.remove(mSettingsCategory.getPreference(i)); parent.preferences.remove(mSettingsCategory.getPreference(i));
} }
mSettingsCategory.removeAll(); mSettingsCategory.removeAll();
for (int i = 0; i < mAutoFireCategory.getPreferenceCount(); i++) {
parent.preferences.remove(mAutoFireCategory.getPreference(i));
}
mAutoFireCategory.removeAll();
for (int i = 0; i < mAutoFireBindingsCategory.getPreferenceCount(); i++) {
parent.preferences.remove(mAutoFireBindingsCategory.getPreference(i));
}
mAutoFireBindingsCategory.removeAll();
} }
private void clearBindings() { private void clearBindings() {

View file

@ -27,6 +27,7 @@ public final class TouchscreenControllerButtonView extends View {
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 int mAutoFireSlot = -1;
private Hotkey mHotkey = Hotkey.NONE; private Hotkey mHotkey = Hotkey.NONE;
private String mConfigName; private String mConfigName;
private boolean mDefaultVisibility = true; private boolean mDefaultVisibility = true;
@ -113,6 +114,11 @@ public final class TouchscreenControllerButtonView extends View {
mButtonCode = code; mButtonCode = code;
} }
public void setAutoFireSlot(int controllerIndex, int slot) {
mControllerIndex = controllerIndex;
mAutoFireSlot = slot;
}
public void setHotkey(Hotkey hotkey) { public void setHotkey(Hotkey hotkey) {
mHotkey = hotkey; mHotkey = hotkey;
} }
@ -135,24 +141,27 @@ public final class TouchscreenControllerButtonView extends View {
} }
private void updateControllerState() { private void updateControllerState() {
final AndroidHostInterface hi = AndroidHostInterface.getInstance();
if (mButtonCode >= 0) if (mButtonCode >= 0)
AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, mButtonCode, mPressed); hi.setControllerButtonState(mControllerIndex, mButtonCode, mPressed);
if (mAutoFireSlot >= 0)
hi.setControllerAutoFireState(mControllerIndex, mAutoFireSlot, mPressed);
switch (mHotkey) switch (mHotkey)
{ {
case FAST_FORWARD: case FAST_FORWARD:
AndroidHostInterface.getInstance().setFastForwardEnabled(mPressed); hi.setFastForwardEnabled(mPressed);
break; break;
case ANALOG_TOGGLE: { case ANALOG_TOGGLE: {
if (!mPressed) if (!mPressed)
AndroidHostInterface.getInstance().toggleControllerAnalogMode(); hi.toggleControllerAnalogMode();
} }
break; break;
case OPEN_PAUSE_MENU: { case OPEN_PAUSE_MENU: {
if (!mPressed) if (!mPressed)
AndroidHostInterface.getInstance().getEmulationActivity().openPauseMenu(); hi.getEmulationActivity().openPauseMenu();
} }
break; break;

View file

@ -308,6 +308,19 @@ public class TouchscreenControllerView extends FrameLayout {
linkAxis(mMainView, R.id.controller_axis_right, "RightAxis", "Right", true); linkAxis(mMainView, R.id.controller_axis_right, "RightAxis", "Right", true);
// GunCon
linkButton(mMainView, R.id.controller_button_a, "AButton", "A", true, true);
linkButton(mMainView, R.id.controller_button_b, "BButton", "B", true, true);
if (pointerButtonName != null)
linkPointer(pointerButtonName);
// Turbo/autofire buttons
linkAutoFireButton(mMainView, R.id.controller_button_autofire_1, "AutoFire1", 0, false);
linkAutoFireButton(mMainView, R.id.controller_button_autofire_2, "AutoFire2", 1, false);
linkAutoFireButton(mMainView, R.id.controller_button_autofire_3, "AutoFire3", 2, false);
linkAutoFireButton(mMainView, R.id.controller_button_autofire_4, "AutoFire4", 3, false);
// Hotkeys
linkHotkeyButton(mMainView, R.id.controller_button_fast_forward, "FastForward", linkHotkeyButton(mMainView, R.id.controller_button_fast_forward, "FastForward",
TouchscreenControllerButtonView.Hotkey.FAST_FORWARD, false); TouchscreenControllerButtonView.Hotkey.FAST_FORWARD, false);
linkHotkeyButton(mMainView, R.id.controller_button_analog, "AnalogToggle", linkHotkeyButton(mMainView, R.id.controller_button_analog, "AnalogToggle",
@ -315,11 +328,6 @@ public class TouchscreenControllerView extends FrameLayout {
linkHotkeyButton(mMainView, R.id.controller_button_pause, "OpenPauseMenu", linkHotkeyButton(mMainView, R.id.controller_button_pause, "OpenPauseMenu",
TouchscreenControllerButtonView.Hotkey.OPEN_PAUSE_MENU, true); TouchscreenControllerButtonView.Hotkey.OPEN_PAUSE_MENU, true);
linkButton(mMainView, R.id.controller_button_a, "AButton", "A", true, true);
linkButton(mMainView, R.id.controller_button_b, "BButton", "B", true, true);
if (pointerButtonName != null)
linkPointer(pointerButtonName);
reloadButtonSettings(); reloadButtonSettings();
updateOpacity(); updateOpacity();
requestLayout(); requestLayout();
@ -412,6 +420,19 @@ public class TouchscreenControllerView extends FrameLayout {
buttonView.setConfigName(configName); buttonView.setConfigName(configName);
buttonView.setDefaultVisibility(defaultVisibility); buttonView.setDefaultVisibility(defaultVisibility);
buttonView.setHotkey(hotkey); buttonView.setHotkey(hotkey);
buttonView.setIsGlidable(false);
mButtonViews.add(buttonView);
}
private void linkAutoFireButton(View view, int id, String configName, int slot, boolean defaultVisibility) {
TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id);
if (buttonView == null)
return;
buttonView.setConfigName(configName);
buttonView.setDefaultVisibility(defaultVisibility);
buttonView.setAutoFireSlot(mControllerIndex, slot);
buttonView.setIsGlidable(true);
mButtonViews.add(buttonView); mButtonViews.add(buttonView);
} }

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M1.086,50.028a48.94,48.712 0,1 0,97.88 0a48.94,48.712 0,1 0,-97.88 0z"
android:strokeWidth="1.5"
android:fillColor="#000000"
android:fillAlpha="0"
android:strokeColor="#c8c8c8"/>
<path
android:pathData="M40.04,63.464H36.23V36.582H26.79V33.238H49.438v3.344h-9.398z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M64.932,63.464H61.291V42.339q0,-1.82 0.042,-2.921 0.042,-1.101 0.127,-2.286 -0.677,0.677 -1.228,1.143 -0.55,0.466 -1.397,1.185l-3.217,2.625 -1.947,-2.498 8.17,-6.35h3.09z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M1.086,50.028a48.94,48.712 0,1 0,97.88 0a48.94,48.712 0,1 0,-97.88 0z"
android:strokeWidth="1.5"
android:fillColor="#c9c9c9"
android:strokeColor="#c8c8c8"/>
<path
android:pathData="M40.04,63.464H36.23V36.582H26.79V33.238H49.438v3.344h-9.398z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M64.932,63.464H61.291V42.339q0,-1.82 0.042,-2.921 0.042,-1.101 0.127,-2.286 -0.677,0.677 -1.228,1.143 -0.55,0.466 -1.397,1.185l-3.217,2.625 -1.947,-2.498 8.17,-6.35h3.09z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M1.086,50.028a48.94,48.712 0,1 0,97.88 0a48.94,48.712 0,1 0,-97.88 0z"
android:strokeWidth="1.5"
android:fillColor="#000000"
android:fillAlpha="0"
android:strokeColor="#c8c8c8"/>
<path
android:pathData="M40.04,63.464H36.23V36.582H26.79V33.238H49.438v3.344h-9.398z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M71.917,63.464H51.936v-3.09l7.916,-8.001q2.286,-2.286 3.852,-4.064 1.566,-1.778 2.371,-3.471 0.804,-1.736 0.804,-3.768 0,-2.498 -1.482,-3.768 -1.482,-1.312 -3.852,-1.312 -2.201,0 -3.895,0.762 -1.651,0.762 -3.387,2.117l-1.99,-2.498q1.778,-1.482 4.064,-2.498 2.328,-1.058 5.207,-1.058 4.233,0 6.689,2.159 2.455,2.117 2.455,5.884 0,2.371 -0.974,4.445 -0.974,2.074 -2.709,4.106 -1.736,1.99 -4.064,4.276l-6.308,6.223v0.169h15.282z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M1.086,50.028a48.94,48.712 0,1 0,97.88 0a48.94,48.712 0,1 0,-97.88 0z"
android:strokeWidth="1.5"
android:fillColor="#c9c9c9"
android:strokeColor="#c8c8c8"/>
<path
android:pathData="M40.04,63.464H36.23V36.582H26.79V33.238H49.438v3.344h-9.398z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M71.917,63.464H51.936v-3.09l7.916,-8.001q2.286,-2.286 3.852,-4.064 1.566,-1.778 2.371,-3.471 0.804,-1.736 0.804,-3.768 0,-2.498 -1.482,-3.768 -1.482,-1.312 -3.852,-1.312 -2.201,0 -3.895,0.762 -1.651,0.762 -3.387,2.117l-1.99,-2.498q1.778,-1.482 4.064,-2.498 2.328,-1.058 5.207,-1.058 4.233,0 6.689,2.159 2.455,2.117 2.455,5.884 0,2.371 -0.974,4.445 -0.974,2.074 -2.709,4.106 -1.736,1.99 -4.064,4.276l-6.308,6.223v0.169h15.282z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M1.086,50.028a48.94,48.712 0,1 0,97.88 0a48.94,48.712 0,1 0,-97.88 0z"
android:strokeWidth="1.5"
android:fillColor="#000000"
android:fillAlpha="0"
android:strokeColor="#c8c8c8"/>
<path
android:pathData="M40.04,63.464H36.23V36.582H26.79V33.238H49.438v3.344h-9.398z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="m70.774,40.307q0,3.048 -1.693,4.868 -1.693,1.778 -4.572,2.371v0.169q3.641,0.423 5.419,2.286 1.778,1.863 1.778,4.868 0,2.625 -1.228,4.699 -1.228,2.032 -3.81,3.175 -2.54,1.143 -6.562,1.143 -2.371,0 -4.403,-0.381 -2.032,-0.339 -3.895,-1.27v-3.471q1.905,0.931 4.106,1.482 2.201,0.508 4.233,0.508 4.064,0 5.842,-1.566 1.82,-1.609 1.82,-4.403 0,-2.836 -2.244,-4.064 -2.201,-1.27 -6.223,-1.27h-2.921v-3.175h2.963q3.725,0 5.63,-1.566 1.947,-1.566 1.947,-4.149 0,-2.201 -1.482,-3.387 -1.482,-1.228 -4.022,-1.228 -2.455,0 -4.191,0.72 -1.736,0.72 -3.429,1.82l-1.863,-2.54q1.609,-1.27 3.979,-2.201 2.413,-0.931 5.461,-0.931 4.741,0 7.027,2.117 2.328,2.117 2.328,5.376z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M1.086,50.028a48.94,48.712 0,1 0,97.88 0a48.94,48.712 0,1 0,-97.88 0z"
android:strokeWidth="1.5"
android:fillColor="#c9c9c9"
android:strokeColor="#c8c8c8"/>
<path
android:pathData="M40.04,63.464H36.23V36.582H26.79V33.238H49.438v3.344h-9.398z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="m70.774,40.307q0,3.048 -1.693,4.868 -1.693,1.778 -4.572,2.371v0.169q3.641,0.423 5.419,2.286 1.778,1.863 1.778,4.868 0,2.625 -1.228,4.699 -1.228,2.032 -3.81,3.175 -2.54,1.143 -6.562,1.143 -2.371,0 -4.403,-0.381 -2.032,-0.339 -3.895,-1.27v-3.471q1.905,0.931 4.106,1.482 2.201,0.508 4.233,0.508 4.064,0 5.842,-1.566 1.82,-1.609 1.82,-4.403 0,-2.836 -2.244,-4.064 -2.201,-1.27 -6.223,-1.27h-2.921v-3.175h2.963q3.725,0 5.63,-1.566 1.947,-1.566 1.947,-4.149 0,-2.201 -1.482,-3.387 -1.482,-1.228 -4.022,-1.228 -2.455,0 -4.191,0.72 -1.736,0.72 -3.429,1.82l-1.863,-2.54q1.609,-1.27 3.979,-2.201 2.413,-0.931 5.461,-0.931 4.741,0 7.027,2.117 2.328,2.117 2.328,5.376z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M1.086,50.028a48.94,48.712 0,1 0,97.88 0a48.94,48.712 0,1 0,-97.88 0z"
android:strokeWidth="1.5"
android:fillColor="#000000"
android:fillAlpha="0"
android:strokeColor="#c8c8c8"/>
<path
android:pathData="M40.04,63.464H36.23V36.582H26.79V33.238H49.438v3.344h-9.398z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="m73.272,56.606h-4.403v6.858h-3.598V56.606h-14.478v-3.175l14.224,-20.362h3.852V53.261h4.403zM65.271,43.736q0,-2.201 0.085,-3.725 0.085,-1.566 0.127,-2.963h-0.169q-0.339,0.804 -0.847,1.736 -0.508,0.931 -0.974,1.566L54.433,53.261h10.837z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M1.086,50.028a48.94,48.712 0,1 0,97.88 0a48.94,48.712 0,1 0,-97.88 0z"
android:strokeWidth="1.5"
android:fillColor="#c9c9c9"
android:strokeColor="#c8c8c8"/>
<path
android:pathData="M40.04,63.464H36.23V36.582H26.79V33.238H49.438v3.344h-9.398z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="m73.272,56.606h-4.403v6.858h-3.598V56.606h-14.478v-3.175l14.224,-20.362h3.852V53.261h4.403zM65.271,43.736q0,-2.201 0.085,-3.725 0.085,-1.566 0.127,-2.963h-0.169q-0.339,0.804 -0.847,1.736 -0.508,0.931 -0.974,1.566L54.433,53.261h10.837z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</vector>

View file

@ -169,4 +169,56 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_pause_button" app:pressedDrawable="@drawable/ic_controller_pause_button"
app:unpressedDrawable="@drawable/ic_controller_pause_button" /> app:unpressedDrawable="@drawable/ic_controller_pause_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_1"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t1_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t1_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_2"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t2_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t2_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_3"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t3_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t3_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_4"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t4_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t4_button" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -191,4 +191,56 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_pause_button" app:pressedDrawable="@drawable/ic_controller_pause_button"
app:unpressedDrawable="@drawable/ic_controller_pause_button" /> app:unpressedDrawable="@drawable/ic_controller_pause_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_1"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t1_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t1_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_2"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t2_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t2_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_3"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t3_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t3_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_4"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t4_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t4_button" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -153,4 +153,56 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_pause_button" app:pressedDrawable="@drawable/ic_controller_pause_button"
app:unpressedDrawable="@drawable/ic_controller_pause_button" /> app:unpressedDrawable="@drawable/ic_controller_pause_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_1"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t1_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t1_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_2"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t2_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t2_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_3"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t3_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t3_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_4"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t4_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t4_button" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -52,4 +52,56 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:pressedDrawable="@drawable/ic_controller_pause_button" app:pressedDrawable="@drawable/ic_controller_pause_button"
app:unpressedDrawable="@drawable/ic_controller_pause_button" /> app:unpressedDrawable="@drawable/ic_controller_pause_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_1"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t1_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t1_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_2"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="220dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t2_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t2_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_3"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t3_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t3_button" />
<com.github.stenzek.duckstation.TouchscreenControllerButtonView
android:id="@+id/controller_button_autofire_4"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="280dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:pressedDrawable="@drawable/ic_controller_t4_button_pressed"
app:unpressedDrawable="@drawable/ic_controller_t4_button" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -354,4 +354,9 @@
<string name="settings_summary_cdrom_seek_speedup">Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, at the cost of breaking others.</string> <string name="settings_summary_cdrom_seek_speedup">Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, at the cost of breaking others.</string>
<string name="settings_custom_aspect_ratio">Custom Aspect Ratio</string> <string name="settings_custom_aspect_ratio">Custom Aspect Ratio</string>
<string name="settings_summary_custom_aspect_ratio">Used when aspect ratio is set to Custom.</string> <string name="settings_summary_custom_aspect_ratio">Used when aspect ratio is set to Custom.</string>
<string name="controller_settings_category_auto_fire_buttons">Auto Fire Buttons</string>
<string name="controller_settings_category_auto_fire_bindings">Auto Fire Triggers</string>
<string name="controller_settings_auto_fire_n_button">Auto Fire %d Button</string>
<string name="controller_settings_auto_fire_n_frequency">Auto Fire %d Frequency/Interval</string>
<string name="controller_binding_auto_fire_n">Auto Fire %d</string>
</resources> </resources>