From 22bb64e7b0be2b1e0e4d0408a971e0463460ed63 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Wed, 30 Dec 2020 19:34:00 +1000 Subject: [PATCH] Android: Add hotkey binding support --- .../app/src/cpp/android_host_interface.cpp | 37 +++++++++++++++ .../duckstation/AndroidHostInterface.java | 2 + .../ControllerBindingPreference.java | 35 ++++++++++++--- .../ControllerMappingActivity.java | 45 ++++++++++++++++--- .../stenzek/duckstation/HotkeyInfo.java | 29 ++++++++++++ .../res/drawable/ic_baseline_category_24.xml | 16 +++++++ 6 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 android/app/src/main/java/com/github/stenzek/duckstation/HotkeyInfo.java create mode 100644 android/app/src/main/res/drawable/ic_baseline_category_24.xml diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index fc6958a42..1a0b3a70c 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -1170,6 +1170,43 @@ DEFINE_JNI_ARGS_METHOD(jarray, AndroidHostInterface_getGameListEntries, jobject return entry_array; } +DEFINE_JNI_ARGS_METHOD(jobjectArray , AndroidHostInterface_getHotkeyInfoList, jobject obj) +{ + jclass entry_class = env->FindClass("com/github/stenzek/duckstation/HotkeyInfo"); + Assert(entry_class != nullptr); + + jmethodID entry_constructor = + env->GetMethodID(entry_class, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + Assert(entry_constructor != nullptr); + + AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); + const CommonHostInterface::HotkeyInfoList& hotkeys = hi->GetHotkeyInfoList(); + if (hotkeys.empty()) + return nullptr; + + jobjectArray entry_array = env->NewObjectArray(static_cast(hotkeys.size()), entry_class, nullptr); + Assert(entry_array != nullptr); + + u32 counter = 0; + for (const CommonHostInterface::HotkeyInfo& hk : hotkeys) + { + jstring category = env->NewStringUTF(hk.category.GetCharArray()); + jstring name = env->NewStringUTF(hk.name.GetCharArray()); + jstring display_name = env->NewStringUTF(hk.display_name.GetCharArray()); + + jobject entry_jobject = env->NewObject(entry_class, entry_constructor, category, name, display_name); + + env->SetObjectArrayElement(entry_array, counter++, entry_jobject); + env->DeleteLocalRef(entry_jobject); + env->DeleteLocalRef(display_name); + env->DeleteLocalRef(name); + env->DeleteLocalRef(category); + } + + return entry_array; +} + DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj) { AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); 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 f90617727..b326d7a70 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 @@ -81,6 +81,8 @@ public class AndroidHostInterface { public native boolean loadInputProfile(String name); public native boolean saveInputProfile(String name); + public native HotkeyInfo[] getHotkeyInfoList(); + public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase, AndroidProgressCallback progressCallback); public native GameListEntry[] getGameListEntries(); 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 c40b985ca..cc9468bc8 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 @@ -14,10 +14,16 @@ import androidx.preference.PreferenceViewHolder; import java.util.Set; public class ControllerBindingPreference extends Preference { - private boolean mIsAxis; + private enum Type { + BUTTON, + AXIS, + HOTKEY + } + private String mBindingName; private String mValue; private TextView mValueView; + private Type mType = Type.BUTTON; private static int getIconForButton(String buttonName) { if (buttonName.equals("Up")) { @@ -57,6 +63,10 @@ public class ControllerBindingPreference extends Preference { return R.drawable.ic_baseline_radio_button_checked_24; } + private static int getIconForHotkey(String hotkeyDisplayName) { + return R.drawable.ic_baseline_category_24; + } + public ControllerBindingPreference(Context context, AttributeSet attrs) { this(context, attrs, 0); setWidgetLayoutResource(R.layout.layout_controller_binding_preference); @@ -83,32 +93,47 @@ public class ControllerBindingPreference extends Preference { TextView nameView = ((TextView)holder.findViewById(R.id.controller_binding_name)); mValueView = ((TextView)holder.findViewById(R.id.controller_binding_value)); - iconView.setImageDrawable(ContextCompat.getDrawable(getContext(), getIconForButton(mBindingName))); + int drawableId = R.drawable.ic_baseline_radio_button_checked_24; + switch (mType) + { + case BUTTON: drawableId = getIconForButton(mBindingName); break; + case AXIS: drawableId = getIconForAxis(mBindingName); break; + case HOTKEY: drawableId = getIconForHotkey(mBindingName); break; + } + + iconView.setImageDrawable(ContextCompat.getDrawable(getContext(), drawableId)); nameView.setText(mBindingName); updateValue(); } @Override protected void onClick() { - ControllerBindingDialog dialog = new ControllerBindingDialog(getContext(), mBindingName, getKey(), mValue, mIsAxis); + ControllerBindingDialog dialog = new ControllerBindingDialog(getContext(), mBindingName, getKey(), mValue, (mType == Type.AXIS)); dialog.setOnDismissListener((dismissedDialog) -> updateValue()); dialog.show(); } public void initButton(int controllerIndex, String buttonName) { mBindingName = buttonName; - mIsAxis = false; + mType = Type.BUTTON; setKey(String.format("Controller%d/Button%s", controllerIndex, buttonName)); updateValue(); } public void initAxis(int controllerIndex, String axisName) { mBindingName = axisName; - mIsAxis = true; + mType = Type.AXIS; setKey(String.format("Controller%d/Axis%s", controllerIndex, axisName)); updateValue(); } + public void initHotkey(HotkeyInfo hotkeyInfo) { + mBindingName = hotkeyInfo.getDisplayName(); + mType = Type.HOTKEY; + setKey(hotkeyInfo.getBindingConfigKey()); + updateValue(); + } + private void updateValue(String value) { mValue = value; if (mValueView != null) { diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerMappingActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerMappingActivity.java index dc703cb44..85000cbae 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerMappingActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerMappingActivity.java @@ -151,11 +151,11 @@ public class ControllerMappingActivity extends AppCompatActivity { pref.updateValue(); } - public static class SettingsFragment extends PreferenceFragmentCompat { + public static class ControllerPortFragment extends PreferenceFragmentCompat { private ControllerMappingActivity activity; private int controllerIndex; - public SettingsFragment(ControllerMappingActivity activity, int controllerIndex) { + public ControllerPortFragment(ControllerMappingActivity activity, int controllerIndex) { this.activity = activity; this.controllerIndex = controllerIndex; } @@ -189,6 +189,31 @@ public class ControllerMappingActivity extends AppCompatActivity { } } + public static class HotkeyFragment extends PreferenceFragmentCompat { + private ControllerMappingActivity activity; + private HotkeyInfo[] mHotkeyInfo; + + public HotkeyFragment(ControllerMappingActivity activity) { + this.activity = activity; + this.mHotkeyInfo = AndroidHostInterface.getInstance().getHotkeyInfoList(); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + final PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(getContext()); + if (mHotkeyInfo != null) { + for (HotkeyInfo hotkeyInfo : mHotkeyInfo) { + final ControllerBindingPreference cbp = new ControllerBindingPreference(getContext(), null); + cbp.initHotkey(hotkeyInfo); + ps.addPreference(cbp); + activity.mPreferences.add(cbp); + } + } + + setPreferenceScreen(ps); + } + } + public static class SettingsCollectionFragment extends Fragment { private ControllerMappingActivity activity; private SettingsCollectionAdapter adapter; @@ -211,9 +236,12 @@ public class ControllerMappingActivity extends AppCompatActivity { viewPager.setAdapter(adapter); TabLayout tabLayout = view.findViewById(R.id.tab_layout); - new TabLayoutMediator(tabLayout, viewPager, - (tab, position) -> tab.setText(String.format("Port %d", position + 1)) - ).attach(); + new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { + if (position == NUM_CONTROLLER_PORTS) + tab.setText("Hotkeys"); + else + tab.setText(String.format("Port %d", position + 1)); + }).attach(); } } @@ -228,12 +256,15 @@ public class ControllerMappingActivity extends AppCompatActivity { @NonNull @Override public Fragment createFragment(int position) { - return new SettingsFragment(activity, position + 1); + if (position != NUM_CONTROLLER_PORTS) + return new ControllerPortFragment(activity, position + 1); + else + return new HotkeyFragment(activity); } @Override public int getItemCount() { - return NUM_CONTROLLER_PORTS; + return NUM_CONTROLLER_PORTS + 1; } } } \ No newline at end of file diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/HotkeyInfo.java b/android/app/src/main/java/com/github/stenzek/duckstation/HotkeyInfo.java new file mode 100644 index 000000000..f16ce9f15 --- /dev/null +++ b/android/app/src/main/java/com/github/stenzek/duckstation/HotkeyInfo.java @@ -0,0 +1,29 @@ +package com.github.stenzek.duckstation; + +public class HotkeyInfo { + private String mCategory; + private String mName; + private String mDisplayName; + + public HotkeyInfo(String category, String name, String displayName) { + mCategory = category; + mName = name; + mDisplayName = displayName; + } + + public String getCategory() { + return mCategory; + } + + public String getName() { + return mName; + } + + public String getDisplayName() { + return mDisplayName; + } + + public String getBindingConfigKey() { + return String.format("Hotkeys/%s", mName); + } +} diff --git a/android/app/src/main/res/drawable/ic_baseline_category_24.xml b/android/app/src/main/res/drawable/ic_baseline_category_24.xml new file mode 100644 index 000000000..b45741e54 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_baseline_category_24.xml @@ -0,0 +1,16 @@ + + + + +