diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index 5a4e1ea74..b850aef33 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -465,7 +465,6 @@ void AndroidHostInterface::EmulationThreadEntryPoint(JNIEnv* env, jobject emulat emulation_activity = env->NewGlobalRef(emulation_activity); Assert(emulation_activity != nullptr); - { std::unique_lock lock(m_mutex); m_emulation_thread_running.store(true); @@ -718,7 +717,8 @@ void AndroidHostInterface::OnRunningGameChanged(const std::string& path, CDImage if (!cover_path_str.empty()) 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) env->DeleteLocalRef(cover_path); @@ -998,7 +998,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) (s_AndroidHostInterface_field_mNativePointer = env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr || (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 = env->GetMethodID(s_AndroidHostInterface_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || (s_AndroidHostInterface_method_reportMessage = @@ -1014,7 +1015,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) (s_EmulationActivity_method_onEmulationStopped = env->GetMethodID(s_EmulationActivity_class, "onEmulationStopped", "()V")) == nullptr || (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")) == nullptr || (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); } +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, jstring button_name) { diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java index d77187484..48d70f7d3 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java @@ -79,6 +79,8 @@ public class AndroidHostInterface { 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 static native int getControllerButtonCode(String controllerType, String buttonName); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingPreference.java b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingPreference.java index a498c5c43..0aa4ad707 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingPreference.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingPreference.java @@ -177,6 +177,15 @@ public class ControllerBindingPreference extends Preference { 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) { final int index = value.indexOf('/'); String device, binding; diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsCollectionFragment.java b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsCollectionFragment.java index 90c04aa8c..f3a2c6925 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsCollectionFragment.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsCollectionFragment.java @@ -1,16 +1,20 @@ package com.github.stenzek.duckstation; +import android.annotation.SuppressLint; +import android.app.Dialog; import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.preference.ListPreference; import androidx.preference.Preference; @@ -18,6 +22,7 @@ import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; +import androidx.preference.SeekBarPreference; import androidx.preference.SwitchPreferenceCompat; import androidx.viewpager2.adapter.FragmentStateAdapter; 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_SUB_CONTROLLER_PORTS = 4; 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 { void onChanged(); @@ -150,6 +156,8 @@ public class ControllerSettingsCollectionFragment extends Fragment { private PreferenceCategory mButtonsCategory; private PreferenceCategory mAxisCategory; private PreferenceCategory mSettingsCategory; + private PreferenceCategory mAutoFireCategory; + private PreferenceCategory mAutoFireBindingsCategory; public ControllerPortFragment(ControllerSettingsCollectionFragment parent, int controllerIndex) { this.parent = parent; @@ -181,6 +189,14 @@ public class ControllerSettingsCollectionFragment extends Fragment { 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() { final PreferenceScreen ps = getPreferenceScreen(); final SharedPreferences sp = getPreferenceManager().getSharedPreferences(); @@ -236,23 +252,34 @@ public class ControllerSettingsCollectionFragment extends Fragment { ps.addPreference(clearBindingsPreference); 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); ps.addPreference(mButtonsCategory); 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); ps.addPreference(mAxisCategory); 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); 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); } + @SuppressLint("DefaultLocale") private void createPreferences(String controllerType) { final PreferenceScreen ps = getPreferenceScreen(); final SharedPreferences sp = getPreferenceManager().getSharedPreferences(); @@ -295,6 +322,33 @@ public class ControllerSettingsCollectionFragment extends Fragment { 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)); } + + 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() { @@ -312,6 +366,16 @@ public class ControllerSettingsCollectionFragment extends Fragment { parent.preferences.remove(mSettingsCategory.getPreference(i)); } 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() { 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 a23deb056..25f160117 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 @@ -27,6 +27,7 @@ public final class TouchscreenControllerButtonView extends View { private boolean mHapticFeedback = false; private int mControllerIndex = -1; private int mButtonCode = -1; + private int mAutoFireSlot = -1; private Hotkey mHotkey = Hotkey.NONE; private String mConfigName; private boolean mDefaultVisibility = true; @@ -113,6 +114,11 @@ public final class TouchscreenControllerButtonView extends View { mButtonCode = code; } + public void setAutoFireSlot(int controllerIndex, int slot) { + mControllerIndex = controllerIndex; + mAutoFireSlot = slot; + } + public void setHotkey(Hotkey hotkey) { mHotkey = hotkey; } @@ -135,24 +141,27 @@ public final class TouchscreenControllerButtonView extends View { } private void updateControllerState() { + final AndroidHostInterface hi = AndroidHostInterface.getInstance(); if (mButtonCode >= 0) - AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, mButtonCode, mPressed); + hi.setControllerButtonState(mControllerIndex, mButtonCode, mPressed); + if (mAutoFireSlot >= 0) + hi.setControllerAutoFireState(mControllerIndex, mAutoFireSlot, mPressed); switch (mHotkey) { case FAST_FORWARD: - AndroidHostInterface.getInstance().setFastForwardEnabled(mPressed); + hi.setFastForwardEnabled(mPressed); break; case ANALOG_TOGGLE: { if (!mPressed) - AndroidHostInterface.getInstance().toggleControllerAnalogMode(); + hi.toggleControllerAnalogMode(); } break; case OPEN_PAUSE_MENU: { if (!mPressed) - AndroidHostInterface.getInstance().getEmulationActivity().openPauseMenu(); + hi.getEmulationActivity().openPauseMenu(); } break; 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 c7ec62228..8a94b701e 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 @@ -308,6 +308,19 @@ public class TouchscreenControllerView extends FrameLayout { 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", TouchscreenControllerButtonView.Hotkey.FAST_FORWARD, false); 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", 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(); updateOpacity(); requestLayout(); @@ -412,6 +420,19 @@ public class TouchscreenControllerView extends FrameLayout { buttonView.setConfigName(configName); buttonView.setDefaultVisibility(defaultVisibility); 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); } diff --git a/android/app/src/main/res/drawable/ic_controller_t1_button.xml b/android/app/src/main/res/drawable/ic_controller_t1_button.xml new file mode 100644 index 000000000..2f342f01c --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_t1_button.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_controller_t1_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_t1_button_pressed.xml new file mode 100644 index 000000000..bf3ffaf35 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_t1_button_pressed.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_controller_t2_button.xml b/android/app/src/main/res/drawable/ic_controller_t2_button.xml new file mode 100644 index 000000000..abddf7071 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_t2_button.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_controller_t2_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_t2_button_pressed.xml new file mode 100644 index 000000000..2b07da75f --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_t2_button_pressed.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_controller_t3_button.xml b/android/app/src/main/res/drawable/ic_controller_t3_button.xml new file mode 100644 index 000000000..d9b001f00 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_t3_button.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_controller_t3_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_t3_button_pressed.xml new file mode 100644 index 000000000..78465f7fd --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_t3_button_pressed.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_controller_t4_button.xml b/android/app/src/main/res/drawable/ic_controller_t4_button.xml new file mode 100644 index 000000000..87f5e6726 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_t4_button.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_controller_t4_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_t4_button_pressed.xml new file mode 100644 index 000000000..b7da1f1b2 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_controller_t4_button_pressed.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/android/app/src/main/res/layout/layout_touchscreen_controller_analog_stick.xml b/android/app/src/main/res/layout/layout_touchscreen_controller_analog_stick.xml index b36cb67e1..534c4197d 100644 --- a/android/app/src/main/res/layout/layout_touchscreen_controller_analog_stick.xml +++ b/android/app/src/main/res/layout/layout_touchscreen_controller_analog_stick.xml @@ -169,4 +169,56 @@ app:layout_constraintEnd_toEndOf="parent" app:pressedDrawable="@drawable/ic_controller_pause_button" app:unpressedDrawable="@drawable/ic_controller_pause_button" /> + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/layout_touchscreen_controller_analog_sticks.xml b/android/app/src/main/res/layout/layout_touchscreen_controller_analog_sticks.xml index 389f55266..a12716e99 100644 --- a/android/app/src/main/res/layout/layout_touchscreen_controller_analog_sticks.xml +++ b/android/app/src/main/res/layout/layout_touchscreen_controller_analog_sticks.xml @@ -191,4 +191,56 @@ app:layout_constraintEnd_toEndOf="parent" app:pressedDrawable="@drawable/ic_controller_pause_button" app:unpressedDrawable="@drawable/ic_controller_pause_button" /> + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/layout_touchscreen_controller_digital.xml b/android/app/src/main/res/layout/layout_touchscreen_controller_digital.xml index 2cf99ca02..7cb449fe3 100644 --- a/android/app/src/main/res/layout/layout_touchscreen_controller_digital.xml +++ b/android/app/src/main/res/layout/layout_touchscreen_controller_digital.xml @@ -153,4 +153,56 @@ app:layout_constraintEnd_toEndOf="parent" app:pressedDrawable="@drawable/ic_controller_pause_button" app:unpressedDrawable="@drawable/ic_controller_pause_button" /> + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/layout_touchscreen_controller_lightgun.xml b/android/app/src/main/res/layout/layout_touchscreen_controller_lightgun.xml index 110969741..3b3b73243 100644 --- a/android/app/src/main/res/layout/layout_touchscreen_controller_lightgun.xml +++ b/android/app/src/main/res/layout/layout_touchscreen_controller_lightgun.xml @@ -52,4 +52,56 @@ app:layout_constraintEnd_toEndOf="parent" app:pressedDrawable="@drawable/ic_controller_pause_button" app:unpressedDrawable="@drawable/ic_controller_pause_button" /> + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index b8e82b1f2..5a20960bc 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -354,4 +354,9 @@ Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, at the cost of breaking others. Custom Aspect Ratio Used when aspect ratio is set to Custom. + Auto Fire Buttons + Auto Fire Triggers + Auto Fire %d Button + Auto Fire %d Frequency/Interval + Auto Fire %d