mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-30 15:32:47 +00:00
Android: Add input profile management
Co-authored-by: Charles Lombardo <clombardo169@gmail.com>
This commit is contained in:
parent
7ef229d908
commit
1eeded23df
@ -35,6 +35,10 @@ public class EmulatedController
|
||||
|
||||
public native void clearSettings();
|
||||
|
||||
public native void loadProfile(String path);
|
||||
|
||||
public native void saveProfile(String path);
|
||||
|
||||
public static native EmulatedController getGcPad(int controllerIndex);
|
||||
|
||||
public static native EmulatedController getWiimote(int controllerIndex);
|
||||
|
@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding;
|
||||
|
||||
public final class ProfileAdapter extends RecyclerView.Adapter<ProfileViewHolder>
|
||||
{
|
||||
private final Context mContext;
|
||||
private final ProfileDialogPresenter mPresenter;
|
||||
|
||||
private final String[] mStockProfileNames;
|
||||
private final String[] mUserProfileNames;
|
||||
|
||||
public ProfileAdapter(Context context, ProfileDialogPresenter presenter)
|
||||
{
|
||||
mContext = context;
|
||||
mPresenter = presenter;
|
||||
|
||||
mStockProfileNames = presenter.getProfileNames(true);
|
||||
mUserProfileNames = presenter.getProfileNames(false);
|
||||
}
|
||||
|
||||
@NonNull @Override
|
||||
public ProfileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
|
||||
ListItemProfileBinding binding = ListItemProfileBinding.inflate(inflater, parent, false);
|
||||
return new ProfileViewHolder(mPresenter, binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ProfileViewHolder holder, int position)
|
||||
{
|
||||
if (position < mStockProfileNames.length)
|
||||
{
|
||||
holder.bind(mStockProfileNames[position], true);
|
||||
return;
|
||||
}
|
||||
|
||||
position -= mStockProfileNames.length;
|
||||
|
||||
if (position < mUserProfileNames.length)
|
||||
{
|
||||
holder.bind(mUserProfileNames[position], false);
|
||||
return;
|
||||
}
|
||||
|
||||
holder.bindAsEmpty(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount()
|
||||
{
|
||||
return mStockProfileNames.length + mUserProfileNames.length + 1;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogInputProfilesBinding
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
||||
|
||||
class ProfileDialog : BottomSheetDialogFragment() {
|
||||
private var presenter: ProfileDialogPresenter? = null
|
||||
|
||||
private var _binding: DialogInputProfilesBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val menuTag: MenuTag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
requireArguments().getSerializable(KEY_MENU_TAG, MenuTag::class.java) as MenuTag
|
||||
} else {
|
||||
requireArguments().getSerializable(KEY_MENU_TAG) as MenuTag
|
||||
}
|
||||
|
||||
presenter = ProfileDialogPresenter(this, menuTag)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = DialogInputProfilesBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.profileList.adapter = ProfileAdapter(context, presenter)
|
||||
binding.profileList.layoutManager = LinearLayoutManager(context)
|
||||
val divider = MaterialDividerItemDecoration(requireActivity(), LinearLayoutManager.VERTICAL)
|
||||
divider.isLastItemDecorated = false
|
||||
binding.profileList.addItemDecoration(divider)
|
||||
|
||||
// You can't expand a bottom sheet with a controller/remote/other non-touch devices
|
||||
val behavior: BottomSheetBehavior<View> = BottomSheetBehavior.from(view.parent as View)
|
||||
if (!resources.getBoolean(R.bool.hasTouch)) {
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_MENU_TAG = "menu_tag"
|
||||
|
||||
@JvmStatic
|
||||
fun create(menuTag: MenuTag): ProfileDialog {
|
||||
val dialog = ProfileDialog()
|
||||
val args = Bundle()
|
||||
args.putSerializable(KEY_MENU_TAG, menuTag)
|
||||
dialog.arguments = args
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView;
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.Collator;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class ProfileDialogPresenter
|
||||
{
|
||||
private static final String EXTENSION = ".ini";
|
||||
|
||||
private final Context mContext;
|
||||
private final DialogFragment mDialog;
|
||||
private final MenuTag mMenuTag;
|
||||
|
||||
public ProfileDialogPresenter(DialogFragment dialog, MenuTag menuTag)
|
||||
{
|
||||
mContext = dialog.getContext();
|
||||
mDialog = dialog;
|
||||
mMenuTag = menuTag;
|
||||
}
|
||||
|
||||
public String[] getProfileNames(boolean stock)
|
||||
{
|
||||
File[] profiles = new File(getProfileDirectoryPath(stock)).listFiles(
|
||||
file -> !file.isDirectory() && file.getName().endsWith(EXTENSION));
|
||||
|
||||
if (profiles == null)
|
||||
return new String[0];
|
||||
|
||||
return Arrays.stream(profiles)
|
||||
.map(file -> file.getName().substring(0, file.getName().length() - EXTENSION.length()))
|
||||
.sorted(Collator.getInstance())
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public void loadProfile(@NonNull String profileName, boolean stock)
|
||||
{
|
||||
new MaterialAlertDialogBuilder(mContext)
|
||||
.setMessage(mContext.getString(R.string.input_profile_confirm_load, profileName))
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) ->
|
||||
{
|
||||
mMenuTag.getCorrespondingEmulatedController()
|
||||
.loadProfile(getProfilePath(profileName, stock));
|
||||
((SettingsActivityView) mDialog.requireActivity()).onControllerSettingsChanged();
|
||||
mDialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void saveProfile(@NonNull String profileName)
|
||||
{
|
||||
// If the user is saving over an existing profile, we should show an overwrite warning.
|
||||
// If the user is creating a new profile, we normally shouldn't show a warning,
|
||||
// but if they've entered the name of an existing profile, we should shown an overwrite warning.
|
||||
|
||||
String profilePath = getProfilePath(profileName, false);
|
||||
if (!new File(profilePath).exists())
|
||||
{
|
||||
mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath);
|
||||
mDialog.dismiss();
|
||||
}
|
||||
else
|
||||
{
|
||||
new MaterialAlertDialogBuilder(mContext)
|
||||
.setMessage(mContext.getString(R.string.input_profile_confirm_save, profileName))
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) ->
|
||||
{
|
||||
mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath);
|
||||
mDialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
public void saveProfileAndPromptForName()
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
|
||||
DialogInputStringBinding binding = DialogInputStringBinding.inflate(inflater);
|
||||
TextInputEditText input = binding.input;
|
||||
|
||||
new MaterialAlertDialogBuilder(mContext)
|
||||
.setView(binding.getRoot())
|
||||
.setPositiveButton(R.string.ok, (dialogInterface, i) ->
|
||||
saveProfile(input.getText().toString()))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void deleteProfile(@NonNull String profileName)
|
||||
{
|
||||
new MaterialAlertDialogBuilder(mContext)
|
||||
.setMessage(mContext.getString(R.string.input_profile_confirm_delete, profileName))
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) ->
|
||||
{
|
||||
new File(getProfilePath(profileName, false)).delete();
|
||||
mDialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private String getProfileDirectoryName()
|
||||
{
|
||||
if (mMenuTag.isGCPadMenu())
|
||||
return "GCPad";
|
||||
else if (mMenuTag.isWiimoteMenu())
|
||||
return "Wiimote";
|
||||
else
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private String getProfileDirectoryPath(boolean stock)
|
||||
{
|
||||
if (stock)
|
||||
{
|
||||
return DirectoryInitialization.getSysDirectory() + "/Profiles/" + getProfileDirectoryName() +
|
||||
'/';
|
||||
}
|
||||
else
|
||||
{
|
||||
return DirectoryInitialization.getUserDirectory() + "/Config/Profiles/" +
|
||||
getProfileDirectoryName() + '/';
|
||||
}
|
||||
}
|
||||
|
||||
private String getProfilePath(String profileName, boolean stock)
|
||||
{
|
||||
return getProfileDirectoryPath(stock) + profileName + EXTENSION;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding;
|
||||
|
||||
public class ProfileViewHolder extends RecyclerView.ViewHolder
|
||||
{
|
||||
private final ProfileDialogPresenter mPresenter;
|
||||
private final ListItemProfileBinding mBinding;
|
||||
|
||||
private String mProfileName;
|
||||
private boolean mStock;
|
||||
|
||||
public ProfileViewHolder(@NonNull ProfileDialogPresenter presenter,
|
||||
@NonNull ListItemProfileBinding binding)
|
||||
{
|
||||
super(binding.getRoot());
|
||||
|
||||
mPresenter = presenter;
|
||||
mBinding = binding;
|
||||
|
||||
binding.buttonLoad.setOnClickListener(view -> loadProfile());
|
||||
binding.buttonSave.setOnClickListener(view -> saveProfile());
|
||||
binding.buttonDelete.setOnClickListener(view -> deleteProfile());
|
||||
}
|
||||
|
||||
public void bind(String profileName, boolean stock)
|
||||
{
|
||||
mProfileName = profileName;
|
||||
mStock = stock;
|
||||
|
||||
mBinding.textName.setText(profileName);
|
||||
|
||||
mBinding.buttonLoad.setVisibility(View.VISIBLE);
|
||||
mBinding.buttonSave.setVisibility(stock ? View.GONE : View.VISIBLE);
|
||||
mBinding.buttonDelete.setVisibility(stock ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
public void bindAsEmpty(Context context)
|
||||
{
|
||||
mProfileName = null;
|
||||
mStock = false;
|
||||
|
||||
mBinding.textName.setText(context.getText(R.string.input_profile_new));
|
||||
|
||||
mBinding.buttonLoad.setVisibility(View.GONE);
|
||||
mBinding.buttonSave.setVisibility(View.VISIBLE);
|
||||
mBinding.buttonDelete.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void loadProfile()
|
||||
{
|
||||
mPresenter.loadProfile(mProfileName, mStock);
|
||||
}
|
||||
|
||||
private void saveProfile()
|
||||
{
|
||||
if (mProfileName == null)
|
||||
mPresenter.saveProfileAndPromptForName();
|
||||
else
|
||||
mPresenter.saveProfile(mProfileName);
|
||||
}
|
||||
|
||||
private void deleteProfile()
|
||||
{
|
||||
mPresenter.deleteProfile(mProfileName);
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ package org.dolphinemu.dolphinemu.features.settings.ui;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
|
||||
|
||||
public enum MenuTag
|
||||
{
|
||||
SETTINGS("settings"),
|
||||
@ -88,6 +90,16 @@ public enum MenuTag
|
||||
return subType;
|
||||
}
|
||||
|
||||
public EmulatedController getCorrespondingEmulatedController()
|
||||
{
|
||||
if (isGCPadMenu())
|
||||
return EmulatedController.getGcPad(getSubType());
|
||||
else if (isWiimoteMenu())
|
||||
return EmulatedController.getWiimote(getSubType());
|
||||
else
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean isSerialPort1Menu()
|
||||
{
|
||||
return this == CONFIG_SERIALPORT1;
|
||||
@ -143,7 +155,8 @@ public enum MenuTag
|
||||
{
|
||||
for (MenuTag menuTag : MenuTag.values())
|
||||
{
|
||||
if (menuTag.tag.equals(tag) && menuTag.subType == subtype) return menuTag;
|
||||
if (menuTag.tag.equals(tag) && menuTag.subType == subtype)
|
||||
return menuTag;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("You are asking for a menu that is not available or " +
|
||||
|
@ -19,6 +19,7 @@ import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@ -44,6 +45,7 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
|
||||
private static final String ARG_IS_WII = "is_wii";
|
||||
private static final String KEY_MAPPING_ALL_DEVICES = "all_devices";
|
||||
private static final String FRAGMENT_TAG = "settings";
|
||||
private static final String FRAGMENT_DIALOG_TAG = "settings_dialog";
|
||||
|
||||
private SettingsActivityPresenter mPresenter;
|
||||
private AlertDialog dialog;
|
||||
@ -191,6 +193,12 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDialogFragment(DialogFragment fragment)
|
||||
{
|
||||
fragment.show(getSupportFragmentManager(), FRAGMENT_DIALOG_TAG);
|
||||
}
|
||||
|
||||
private boolean areSystemAnimationsEnabled()
|
||||
{
|
||||
float duration = Settings.Global.getFloat(
|
||||
@ -314,6 +322,12 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
|
||||
mPresenter.onSettingChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onControllerSettingsChanged()
|
||||
{
|
||||
getFragment().onControllerSettingsChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMenuTagAction(@NonNull MenuTag menuTag, int value)
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ package org.dolphinemu.dolphinemu.features.settings.ui;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
|
||||
@ -21,6 +22,13 @@ public interface SettingsActivityView
|
||||
*/
|
||||
void showSettingsFragment(MenuTag menuTag, Bundle extras, boolean addToStack, String gameId);
|
||||
|
||||
/**
|
||||
* Shows a DialogFragment.
|
||||
*
|
||||
* Only one can be shown at a time.
|
||||
*/
|
||||
void showDialogFragment(DialogFragment fragment);
|
||||
|
||||
/**
|
||||
* Called by a contained Fragment to get access to the Setting HashMap
|
||||
* loaded from disk, so that each Fragment doesn't need to perform its own
|
||||
@ -60,6 +68,14 @@ public interface SettingsActivityView
|
||||
*/
|
||||
void onSettingChanged();
|
||||
|
||||
/**
|
||||
* Refetches the values of all controller settings.
|
||||
*
|
||||
* To be used when loading an input profile or performing some other action that changes all
|
||||
* controller settings at once.
|
||||
*/
|
||||
void onControllerSettingsChanged();
|
||||
|
||||
/**
|
||||
* Called by a containing Fragment to tell the containing Activity that the user wants to open the
|
||||
* MenuTag associated with a setting.
|
||||
|
@ -13,6 +13,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@ -221,6 +222,12 @@ public final class SettingsFragment extends Fragment implements SettingsFragment
|
||||
mActivity.showSettingsFragment(menuKey, null, true, getArguments().getString(ARGUMENT_GAME_ID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDialogFragment(DialogFragment fragment)
|
||||
{
|
||||
mActivity.showDialogFragment(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showToastMessage(String message)
|
||||
{
|
||||
@ -239,6 +246,13 @@ public final class SettingsFragment extends Fragment implements SettingsFragment
|
||||
mActivity.onSettingChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onControllerSettingsChanged()
|
||||
{
|
||||
mAdapter.notifyAllSettingsChanged();
|
||||
mPresenter.updateOldControllerSettingsWarningVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMenuTagAction(@NonNull MenuTag menuTag, int value)
|
||||
{
|
||||
|
@ -24,6 +24,7 @@ import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedCont
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.view.InputDeviceSetting;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
|
||||
import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialog;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AdHocBooleanSetting;
|
||||
@ -1223,6 +1224,9 @@ public final class SettingsFragmentPresenter
|
||||
sl.add(new RunRunnable(mContext, R.string.input_clear, R.string.input_clear_description,
|
||||
R.string.input_reset_warning, 0, true, () -> clearControllerSettings(controller)));
|
||||
|
||||
sl.add(new RunRunnable(mContext, R.string.input_profiles, 0, 0, 0, true,
|
||||
() -> mView.showDialogFragment(ProfileDialog.create(mMenuTag))));
|
||||
|
||||
updateOldControllerSettingsWarningVisibility(controller);
|
||||
}
|
||||
|
||||
@ -1293,6 +1297,11 @@ public final class SettingsFragmentPresenter
|
||||
}
|
||||
}
|
||||
|
||||
public void updateOldControllerSettingsWarningVisibility()
|
||||
{
|
||||
updateOldControllerSettingsWarningVisibility(mMenuTag.getCorrespondingEmulatedController());
|
||||
}
|
||||
|
||||
private void updateOldControllerSettingsWarningVisibility(EmulatedController controller)
|
||||
{
|
||||
String defaultDevice = controller.getDefaultDevice();
|
||||
@ -1306,15 +1315,13 @@ public final class SettingsFragmentPresenter
|
||||
private void loadDefaultControllerSettings(EmulatedController controller)
|
||||
{
|
||||
controller.loadDefaultSettings();
|
||||
mView.getAdapter().notifyAllSettingsChanged();
|
||||
updateOldControllerSettingsWarningVisibility(controller);
|
||||
mView.onControllerSettingsChanged();
|
||||
}
|
||||
|
||||
private void clearControllerSettings(EmulatedController controller)
|
||||
{
|
||||
controller.clearSettings();
|
||||
mView.getAdapter().notifyAllSettingsChanged();
|
||||
updateOldControllerSettingsWarningVisibility(controller);
|
||||
mView.onControllerSettingsChanged();
|
||||
}
|
||||
|
||||
private static int getLogVerbosityEntries()
|
||||
|
@ -3,6 +3,7 @@
|
||||
package org.dolphinemu.dolphinemu.features.settings.ui;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
@ -55,6 +56,8 @@ public interface SettingsFragmentView
|
||||
*/
|
||||
void loadSubMenu(MenuTag menuKey);
|
||||
|
||||
void showDialogFragment(DialogFragment fragment);
|
||||
|
||||
/**
|
||||
* Tell the Fragment to tell the containing activity to display a toast message.
|
||||
*
|
||||
@ -72,6 +75,14 @@ public interface SettingsFragmentView
|
||||
*/
|
||||
void onSettingChanged();
|
||||
|
||||
/**
|
||||
* Refetches the values of all controller settings.
|
||||
*
|
||||
* To be used when loading an input profile or performing some other action that changes all
|
||||
* controller settings at once.
|
||||
*/
|
||||
void onControllerSettingsChanged();
|
||||
|
||||
/**
|
||||
* Have the fragment tell the containing Activity that the user wants to open the MenuTag
|
||||
* associated with a setting.
|
||||
|
@ -45,6 +45,7 @@ public final class DirectoryInitialization
|
||||
new MutableLiveData<>(DirectoryInitializationState.NOT_YET_INITIALIZED);
|
||||
private static volatile boolean areDirectoriesAvailable = false;
|
||||
private static String userPath;
|
||||
private static String sysPath;
|
||||
private static boolean isUsingLegacyUserDirectory = false;
|
||||
|
||||
public enum DirectoryInitializationState
|
||||
@ -153,7 +154,8 @@ public final class DirectoryInitialization
|
||||
}
|
||||
|
||||
// Let the native code know where the Sys directory is.
|
||||
SetSysDirectory(sysDirectory.getPath());
|
||||
sysPath = sysDirectory.getPath();
|
||||
SetSysDirectory(sysPath);
|
||||
}
|
||||
|
||||
private static void deleteDirectoryRecursively(@NonNull final File file)
|
||||
@ -204,6 +206,16 @@ public final class DirectoryInitialization
|
||||
return userPath;
|
||||
}
|
||||
|
||||
public static String getSysDirectory()
|
||||
{
|
||||
if (!areDirectoriesAvailable)
|
||||
{
|
||||
throw new IllegalStateException(
|
||||
"DirectoryInitialization must run before accessing the Sys directory!");
|
||||
}
|
||||
return sysPath;
|
||||
}
|
||||
|
||||
public static File getGameListCache(Context context)
|
||||
{
|
||||
return new File(context.getExternalCacheDir(), "gamelist.cache");
|
||||
|
9
Source/Android/app/src/main/res/drawable/ic_delete.xml
Normal file
9
Source/Android/app/src/main/res/drawable/ic_delete.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M7,21Q6.175,21 5.588,20.413Q5,19.825 5,19V6H4V4H9V3H15V4H20V6H19V19Q19,19.825 18.413,20.413Q17.825,21 17,21ZM17,6H7V19Q7,19 7,19Q7,19 7,19H17Q17,19 17,19Q17,19 17,19ZM9,17H11V8H9ZM13,17H15V8H13ZM7,6V19Q7,19 7,19Q7,19 7,19Q7,19 7,19Q7,19 7,19Z"/>
|
||||
</vector>
|
9
Source/Android/app/src/main/res/drawable/ic_save.xml
Normal file
9
Source/Android/app/src/main/res/drawable/ic_save.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" />
|
||||
</vector>
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/profile_sheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/profile_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
69
Source/Android/app/src/main/res/layout/list_item_profile.xml
Normal file
69
Source/Android/app/src/main/res/layout/list_item_profile.xml
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/root"
|
||||
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="wrap_content"
|
||||
android:paddingHorizontal="@dimen/spacing_medlarge"
|
||||
android:paddingVertical="@dimen/spacing_medlarge">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_marginStart="@dimen/spacing_small"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_delete"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Wii Remote with Motion Plus Pointing" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_delete"
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/spacing_small"
|
||||
android:contentDescription="@string/input_profile_delete"
|
||||
android:tooltipText="@string/input_profile_delete"
|
||||
app:icon="@drawable/ic_delete"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_load"
|
||||
app:layout_constraintStart_toEndOf="@id/text_name"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_load"
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/spacing_small"
|
||||
android:contentDescription="@string/input_profile_load"
|
||||
android:tooltipText="@string/input_profile_load"
|
||||
app:icon="@drawable/ic_load"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_save"
|
||||
app:layout_constraintStart_toEndOf="@id/button_delete"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_save"
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/spacing_small"
|
||||
android:contentDescription="@string/input_profile_save"
|
||||
android:tooltipText="@string/input_profile_save"
|
||||
app:icon="@drawable/ic_save"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/button_load"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -30,6 +30,14 @@
|
||||
<string name="input_device">Device</string>
|
||||
<string name="input_device_all_devices">Create Mappings for Other Devices</string>
|
||||
<string name="input_device_all_devices_description">Detects inputs from all devices, not just the selected device.</string>
|
||||
<string name="input_profiles">Profiles</string>
|
||||
<string name="input_profile_new">(New Profile)</string>
|
||||
<string name="input_profile_load">Load</string>
|
||||
<string name="input_profile_save">Save</string>
|
||||
<string name="input_profile_delete">Delete</string>
|
||||
<string name="input_profile_confirm_load">Do you want to discard your current controller settings and load the profile \"%1$s\"?</string>
|
||||
<string name="input_profile_confirm_save">Do you want to overwrite the profile \"%1$s\"?</string>
|
||||
<string name="input_profile_confirm_delete">Do you want to delete the profile \"%1$s\"?</string>
|
||||
<string name="input_reset_to_default">Default</string>
|
||||
<string name="input_reset_to_default_description">Reset settings for this controller to the default.</string>
|
||||
<string name="input_clear">Clear</string>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
#include "Core/HW/GCPad.h"
|
||||
#include "Core/HW/Wiimote.h"
|
||||
@ -95,6 +96,33 @@ Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedContro
|
||||
controller->UpdateReferences(g_controller_interface);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_loadProfile(
|
||||
JNIEnv* env, jobject obj, jstring j_path)
|
||||
{
|
||||
ControllerEmu::EmulatedController* controller = EmulatedControllerFromJava(env, obj);
|
||||
|
||||
IniFile ini;
|
||||
ini.Load(GetJString(env, j_path));
|
||||
|
||||
controller->LoadConfig(ini.GetOrCreateSection("Profile"));
|
||||
controller->UpdateReferences(g_controller_interface);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_saveProfile(
|
||||
JNIEnv* env, jobject obj, jstring j_path)
|
||||
{
|
||||
const std::string path = GetJString(env, j_path);
|
||||
|
||||
File::CreateFullPath(path);
|
||||
|
||||
IniFile ini;
|
||||
|
||||
EmulatedControllerFromJava(env, obj)->SaveConfig(ini.GetOrCreateSection("Profile"));
|
||||
ini.Save(path);
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getGcPad(
|
||||
JNIEnv* env, jclass, jint controller_index)
|
||||
|
Loading…
x
Reference in New Issue
Block a user