diff options
Diffstat (limited to 'src/android/app/src/main/java/org/citra/citra_emu/features/settings/model')
18 files changed, 1279 insertions, 0 deletions
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.java new file mode 100644 index 000000000..932dcf1d3 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.java @@ -0,0 +1,23 @@ +package org.citra.citra_emu.features.settings.model; + +public final class BooleanSetting extends Setting { + private boolean mValue; + + public BooleanSetting(String key, String section, boolean value) { + super(key, section); + mValue = value; + } + + public boolean getValue() { + return mValue; + } + + public void setValue(boolean value) { + mValue = value; + } + + @Override + public String getValueAsString() { + return mValue ? "True" : "False"; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.java new file mode 100644 index 000000000..275f0ecea --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/FloatSetting.java @@ -0,0 +1,23 @@ +package org.citra.citra_emu.features.settings.model; + +public final class FloatSetting extends Setting { + private float mValue; + + public FloatSetting(String key, String section, float value) { + super(key, section); + mValue = value; + } + + public float getValue() { + return mValue; + } + + public void setValue(float value) { + mValue = value; + } + + @Override + public String getValueAsString() { + return Float.toString(mValue); + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.java new file mode 100644 index 000000000..f712e5bfa --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.java @@ -0,0 +1,23 @@ +package org.citra.citra_emu.features.settings.model; + +public final class IntSetting extends Setting { + private int mValue; + + public IntSetting(String key, String section, int value) { + super(key, section); + mValue = value; + } + + public int getValue() { + return mValue; + } + + public void setValue(int value) { + mValue = value; + } + + @Override + public String getValueAsString() { + return Integer.toString(mValue); + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Setting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Setting.java new file mode 100644 index 000000000..b762847c9 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Setting.java @@ -0,0 +1,42 @@ +package org.citra.citra_emu.features.settings.model; + +/** + * Abstraction for a setting item as read from / written to Citra's configuration ini files. + * These files generally consist of a key/value pair, though the type of value is ambiguous and + * must be inferred at read-time. The type of value determines which child of this class is used + * to represent the Setting. + */ +public abstract class Setting { + private String mKey; + private String mSection; + + /** + * Base constructor. + * + * @param key Everything to the left of the = in a line from the ini file. + * @param section The corresponding recent section header; e.g. [Core] or [Enhancements] without the brackets. + */ + public Setting(String key, String section) { + mKey = key; + mSection = section; + } + + /** + * @return The identifier used to write this setting to the ini file. + */ + public String getKey() { + return mKey; + } + + /** + * @return The name of the header under which this Setting should be written in the ini file. + */ + public String getSection() { + return mSection; + } + + /** + * @return A representation of this Setting's backing value converted to a String (e.g. for serialization). + */ + public abstract String getValueAsString(); +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingSection.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingSection.java new file mode 100644 index 000000000..0a291aa6b --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/SettingSection.java @@ -0,0 +1,55 @@ +package org.citra.citra_emu.features.settings.model; + +import java.util.HashMap; + +/** + * A semantically-related group of Settings objects. These Settings are + * internally stored as a HashMap. + */ +public final class SettingSection { + private String mName; + + private HashMap<String, Setting> mSettings = new HashMap<>(); + + /** + * Create a new SettingSection with no Settings in it. + * + * @param name The header of this section; e.g. [Core] or [Enhancements] without the brackets. + */ + public SettingSection(String name) { + mName = name; + } + + public String getName() { + return mName; + } + + /** + * Convenience method; inserts a value directly into the backing HashMap. + * + * @param setting The Setting to be inserted. + */ + public void putSetting(Setting setting) { + mSettings.put(setting.getKey(), setting); + } + + /** + * Convenience method; gets a value directly from the backing HashMap. + * + * @param key Used to retrieve the Setting. + * @return A Setting object (you should probably cast this before using) + */ + public Setting getSetting(String key) { + return mSettings.get(key); + } + + public HashMap<String, Setting> getSettings() { + return mSettings; + } + + public void mergeSection(SettingSection settingSection) { + for (Setting setting : settingSection.mSettings.values()) { + putSetting(setting); + } + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.java new file mode 100644 index 000000000..9684966f2 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.java @@ -0,0 +1,132 @@ +package org.citra.citra_emu.features.settings.model; + +import android.text.TextUtils; + +import org.citra.citra_emu.CitraApplication; +import org.citra.citra_emu.R; +import org.citra.citra_emu.features.settings.ui.SettingsActivityView; +import org.citra.citra_emu.features.settings.utils.SettingsFile; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class Settings { + public static final String SECTION_PREMIUM = "Premium"; + public static final String SECTION_CORE = "Core"; + public static final String SECTION_SYSTEM = "System"; + public static final String SECTION_CAMERA = "Camera"; + public static final String SECTION_CONTROLS = "Controls"; + public static final String SECTION_RENDERER = "Renderer"; + public static final String SECTION_LAYOUT = "Layout"; + public static final String SECTION_UTILITY = "Utility"; + public static final String SECTION_AUDIO = "Audio"; + public static final String SECTION_DEBUG = "Debug"; + + private String gameId; + + private static final Map<String, List<String>> configFileSectionsMap = new HashMap<>(); + + static { + configFileSectionsMap.put(SettingsFile.FILE_NAME_CONFIG, Arrays.asList(SECTION_PREMIUM, SECTION_CORE, SECTION_SYSTEM, SECTION_CAMERA, SECTION_CONTROLS, SECTION_RENDERER, SECTION_LAYOUT, SECTION_UTILITY, SECTION_AUDIO, SECTION_DEBUG)); + } + + /** + * A HashMap<String, SettingSection> that constructs a new SettingSection instead of returning null + * when getting a key not already in the map + */ + public static final class SettingsSectionMap extends HashMap<String, SettingSection> { + @Override + public SettingSection get(Object key) { + if (!(key instanceof String)) { + return null; + } + + String stringKey = (String) key; + + if (!super.containsKey(stringKey)) { + SettingSection section = new SettingSection(stringKey); + super.put(stringKey, section); + return section; + } + return super.get(key); + } + } + + private HashMap<String, SettingSection> sections = new Settings.SettingsSectionMap(); + + public SettingSection getSection(String sectionName) { + return sections.get(sectionName); + } + + public boolean isEmpty() { + return sections.isEmpty(); + } + + public HashMap<String, SettingSection> getSections() { + return sections; + } + + public void loadSettings(SettingsActivityView view) { + sections = new Settings.SettingsSectionMap(); + loadCitraSettings(view); + + if (!TextUtils.isEmpty(gameId)) { + loadCustomGameSettings(gameId, view); + } + } + + private void loadCitraSettings(SettingsActivityView view) { + for (Map.Entry<String, List<String>> entry : configFileSectionsMap.entrySet()) { + String fileName = entry.getKey(); + sections.putAll(SettingsFile.readFile(fileName, view)); + } + } + + private void loadCustomGameSettings(String gameId, SettingsActivityView view) { + // custom game settings + mergeSections(SettingsFile.readCustomGameSettings(gameId, view)); + } + + private void mergeSections(HashMap<String, SettingSection> updatedSections) { + for (Map.Entry<String, SettingSection> entry : updatedSections.entrySet()) { + if (sections.containsKey(entry.getKey())) { + SettingSection originalSection = sections.get(entry.getKey()); + SettingSection updatedSection = entry.getValue(); + originalSection.mergeSection(updatedSection); + } else { + sections.put(entry.getKey(), entry.getValue()); + } + } + } + + public void loadSettings(String gameId, SettingsActivityView view) { + this.gameId = gameId; + loadSettings(view); + } + + public void saveSettings(SettingsActivityView view) { + if (TextUtils.isEmpty(gameId)) { + view.showToastMessage(CitraApplication.getAppContext().getString(R.string.ini_saved), false); + + for (Map.Entry<String, List<String>> entry : configFileSectionsMap.entrySet()) { + String fileName = entry.getKey(); + List<String> sectionNames = entry.getValue(); + TreeMap<String, SettingSection> iniSections = new TreeMap<>(); + for (String section : sectionNames) { + iniSections.put(section, sections.get(section)); + } + + SettingsFile.saveFile(fileName, iniSections, view); + } + } else { + // custom game settings + view.showToastMessage(CitraApplication.getAppContext().getString(R.string.gameid_saved, gameId), false); + + SettingsFile.saveCustomGameSettings(gameId, sections); + } + + } +}
\ No newline at end of file diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.java new file mode 100644 index 000000000..b906b7010 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.java @@ -0,0 +1,23 @@ +package org.citra.citra_emu.features.settings.model; + +public final class StringSetting extends Setting { + private String mValue; + + public StringSetting(String key, String section, String value) { + super(key, section); + mValue = value; + } + + public String getValue() { + return mValue; + } + + public void setValue(String value) { + mValue = value; + } + + @Override + public String getValueAsString() { + return mValue; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/CheckBoxSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/CheckBoxSetting.java new file mode 100644 index 000000000..baf40709f --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/CheckBoxSetting.java @@ -0,0 +1,80 @@ +package org.citra.citra_emu.features.settings.model.view; + +import org.citra.citra_emu.CitraApplication; +import org.citra.citra_emu.R; +import org.citra.citra_emu.features.settings.model.BooleanSetting; +import org.citra.citra_emu.features.settings.model.IntSetting; +import org.citra.citra_emu.features.settings.model.Setting; +import org.citra.citra_emu.features.settings.ui.SettingsFragmentView; + +public final class CheckBoxSetting extends SettingsItem { + private boolean mDefaultValue; + private boolean mShowPerformanceWarning; + private SettingsFragmentView mView; + + public CheckBoxSetting(String key, String section, int titleId, int descriptionId, + boolean defaultValue, Setting setting) { + super(key, section, setting, titleId, descriptionId); + mDefaultValue = defaultValue; + mShowPerformanceWarning = false; + } + + public CheckBoxSetting(String key, String section, int titleId, int descriptionId, + boolean defaultValue, Setting setting, boolean show_performance_warning, SettingsFragmentView view) { + super(key, section, setting, titleId, descriptionId); + mDefaultValue = defaultValue; + mView = view; + mShowPerformanceWarning = show_performance_warning; + } + + public boolean isChecked() { + if (getSetting() == null) { + return mDefaultValue; + } + + // Try integer setting + try { + IntSetting setting = (IntSetting) getSetting(); + return setting.getValue() == 1; + } catch (ClassCastException exception) { + } + + // Try boolean setting + try { + BooleanSetting setting = (BooleanSetting) getSetting(); + return setting.getValue() == true; + } catch (ClassCastException exception) { + } + + return mDefaultValue; + } + + /** + * Write a value to the backing boolean. If that boolean was previously null, + * initializes a new one and returns it, so it can be added to the Hashmap. + * + * @param checked Pretty self explanatory. + * @return null if overwritten successfully; otherwise, a newly created BooleanSetting. + */ + public IntSetting setChecked(boolean checked) { + // Show a performance warning if the setting has been disabled + if (mShowPerformanceWarning && !checked) { + mView.showToastMessage(CitraApplication.getAppContext().getString(R.string.performance_warning), true); + } + + if (getSetting() == null) { + IntSetting setting = new IntSetting(getKey(), getSection(), checked ? 1 : 0); + setSetting(setting); + return setting; + } else { + IntSetting setting = (IntSetting) getSetting(); + setting.setValue(checked ? 1 : 0); + return null; + } + } + + @Override + public int getType() { + return TYPE_CHECKBOX; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/DateTimeSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/DateTimeSetting.java new file mode 100644 index 000000000..afc3352cc --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/DateTimeSetting.java @@ -0,0 +1,40 @@ +package org.citra.citra_emu.features.settings.model.view; + +import org.citra.citra_emu.features.settings.model.Setting; +import org.citra.citra_emu.features.settings.model.StringSetting; + +public final class DateTimeSetting extends SettingsItem { + private String mDefaultValue; + + public DateTimeSetting(String key, String section, int titleId, int descriptionId, + String defaultValue, Setting setting) { + super(key, section, setting, titleId, descriptionId); + mDefaultValue = defaultValue; + } + + public String getValue() { + if (getSetting() != null) { + StringSetting setting = (StringSetting) getSetting(); + return setting.getValue(); + } else { + return mDefaultValue; + } + } + + public StringSetting setSelectedValue(String datetime) { + if (getSetting() == null) { + StringSetting setting = new StringSetting(getKey(), getSection(), datetime); + setSetting(setting); + return setting; + } else { + StringSetting setting = (StringSetting) getSetting(); + setting.setValue(datetime); + return null; + } + } + + @Override + public int getType() { + return TYPE_DATETIME_SETTING; + } +}
\ No newline at end of file diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/HeaderSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/HeaderSetting.java new file mode 100644 index 000000000..bac8876cd --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/HeaderSetting.java @@ -0,0 +1,14 @@ +package org.citra.citra_emu.features.settings.model.view; + +import org.citra.citra_emu.features.settings.model.Setting; + +public final class HeaderSetting extends SettingsItem { + public HeaderSetting(String key, Setting setting, int titleId, int descriptionId) { + super(key, null, setting, titleId, descriptionId); + } + + @Override + public int getType() { + return SettingsItem.TYPE_HEADER; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.java new file mode 100644 index 000000000..e9141a208 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.java @@ -0,0 +1,382 @@ +package org.citra.citra_emu.features.settings.model.view; + +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.widget.Toast; + +import org.citra.citra_emu.CitraApplication; +import org.citra.citra_emu.NativeLibrary; +import org.citra.citra_emu.R; +import org.citra.citra_emu.features.settings.model.Setting; +import org.citra.citra_emu.features.settings.model.StringSetting; +import org.citra.citra_emu.features.settings.utils.SettingsFile; + +public final class InputBindingSetting extends SettingsItem { + private static final String INPUT_MAPPING_PREFIX = "InputMapping"; + + public InputBindingSetting(String key, String section, int titleId, Setting setting) { + super(key, section, setting, titleId, 0); + } + + public String getValue() { + if (getSetting() == null) { + return ""; + } + + StringSetting setting = (StringSetting) getSetting(); + return setting.getValue(); + } + + /** + * Returns true if this key is for the 3DS Circle Pad + */ + private boolean IsCirclePad() { + switch (getKey()) { + case SettingsFile.KEY_CIRCLEPAD_AXIS_HORIZONTAL: + case SettingsFile.KEY_CIRCLEPAD_AXIS_VERTICAL: + return true; + } + return false; + } + + /** + * Returns true if this key is for a horizontal axis for a 3DS analog stick or D-pad + */ + public boolean IsHorizontalOrientation() { + switch (getKey()) { + case SettingsFile.KEY_CIRCLEPAD_AXIS_HORIZONTAL: + case SettingsFile.KEY_CSTICK_AXIS_HORIZONTAL: + case SettingsFile.KEY_DPAD_AXIS_HORIZONTAL: + return true; + } + return false; + } + + /** + * Returns true if this key is for the 3DS C-Stick + */ + private boolean IsCStick() { + switch (getKey()) { + case SettingsFile.KEY_CSTICK_AXIS_HORIZONTAL: + case SettingsFile.KEY_CSTICK_AXIS_VERTICAL: + return true; + } + return false; + } + + /** + * Returns true if this key is for the 3DS D-Pad + */ + private boolean IsDPad() { + switch (getKey()) { + case SettingsFile.KEY_DPAD_AXIS_HORIZONTAL: + case SettingsFile.KEY_DPAD_AXIS_VERTICAL: + return true; + } + return false; + } + + /** + * Returns true if this key is for the 3DS L/R or ZL/ZR buttons. Note, these are not real + * triggers on the 3DS, but we support them as such on a physical gamepad. + */ + public boolean IsTrigger() { + switch (getKey()) { + case SettingsFile.KEY_BUTTON_L: + case SettingsFile.KEY_BUTTON_R: + case SettingsFile.KEY_BUTTON_ZL: + case SettingsFile.KEY_BUTTON_ZR: + return true; + } + return false; + } + + /** + * Returns true if a gamepad axis can be used to map this key. + */ + public boolean IsAxisMappingSupported() { + return IsCirclePad() || IsCStick() || IsDPad() || IsTrigger(); + } + + /** + * Returns true if a gamepad button can be used to map this key. + */ + private boolean IsButtonMappingSupported() { + return !IsAxisMappingSupported() || IsTrigger(); + } + + /** + * Returns the Citra button code for the settings key. + */ + private int getButtonCode() { + switch (getKey()) { + case SettingsFile.KEY_BUTTON_A: + return NativeLibrary.ButtonType.BUTTON_A; + case SettingsFile.KEY_BUTTON_B: + return NativeLibrary.ButtonType.BUTTON_B; + case SettingsFile.KEY_BUTTON_X: + return NativeLibrary.ButtonType.BUTTON_X; + case SettingsFile.KEY_BUTTON_Y: + return NativeLibrary.ButtonType.BUTTON_Y; + case SettingsFile.KEY_BUTTON_L: + return NativeLibrary.ButtonType.TRIGGER_L; + case SettingsFile.KEY_BUTTON_R: + return NativeLibrary.ButtonType.TRIGGER_R; + case SettingsFile.KEY_BUTTON_ZL: + return NativeLibrary.ButtonType.BUTTON_ZL; + case SettingsFile.KEY_BUTTON_ZR: + return NativeLibrary.ButtonType.BUTTON_ZR; + case SettingsFile.KEY_BUTTON_SELECT: + return NativeLibrary.ButtonType.BUTTON_SELECT; + case SettingsFile.KEY_BUTTON_START: + return NativeLibrary.ButtonType.BUTTON_START; + case SettingsFile.KEY_BUTTON_UP: + return NativeLibrary.ButtonType.DPAD_UP; + case SettingsFile.KEY_BUTTON_DOWN: + return NativeLibrary.ButtonType.DPAD_DOWN; + case SettingsFile.KEY_BUTTON_LEFT: + return NativeLibrary.ButtonType.DPAD_LEFT; + case SettingsFile.KEY_BUTTON_RIGHT: + return NativeLibrary.ButtonType.DPAD_RIGHT; + } + return -1; + } + + /** + * Returns the settings key for the specified Citra button code. + */ + private static String getButtonKey(int buttonCode) { + switch (buttonCode) { + case NativeLibrary.ButtonType.BUTTON_A: + return SettingsFile.KEY_BUTTON_A; + case NativeLibrary.ButtonType.BUTTON_B: + return SettingsFile.KEY_BUTTON_B; + case NativeLibrary.ButtonType.BUTTON_X: + return SettingsFile.KEY_BUTTON_X; + case NativeLibrary.ButtonType.BUTTON_Y: + return SettingsFile.KEY_BUTTON_Y; + case NativeLibrary.ButtonType.TRIGGER_L: + return SettingsFile.KEY_BUTTON_L; + case NativeLibrary.ButtonType.TRIGGER_R: + return SettingsFile.KEY_BUTTON_R; + case NativeLibrary.ButtonType.BUTTON_ZL: + return SettingsFile.KEY_BUTTON_ZL; + case NativeLibrary.ButtonType.BUTTON_ZR: + return SettingsFile.KEY_BUTTON_ZR; + case NativeLibrary.ButtonType.BUTTON_SELECT: + return SettingsFile.KEY_BUTTON_SELECT; + case NativeLibrary.ButtonType.BUTTON_START: + return SettingsFile.KEY_BUTTON_START; + case NativeLibrary.ButtonType.DPAD_UP: + return SettingsFile.KEY_BUTTON_UP; + case NativeLibrary.ButtonType.DPAD_DOWN: + return SettingsFile.KEY_BUTTON_DOWN; + case NativeLibrary.ButtonType.DPAD_LEFT: + return SettingsFile.KEY_BUTTON_LEFT; + case NativeLibrary.ButtonType.DPAD_RIGHT: + return SettingsFile.KEY_BUTTON_RIGHT; + } + return ""; + } + + /** + * Returns the key used to lookup the reverse mapping for this key, which is used to cleanup old + * settings on re-mapping or clearing of a setting. + */ + private String getReverseKey() { + String reverseKey = INPUT_MAPPING_PREFIX + "_ReverseMapping_" + getKey(); + + if (IsAxisMappingSupported() && !IsTrigger()) { + // Triggers are the only axis-supported mappings without orientation + reverseKey += "_" + (IsHorizontalOrientation() ? 0 : 1); + } + + return reverseKey; + } + + /** + * Removes the old mapping for this key from the settings, e.g. on user clearing the setting. + */ + public void removeOldMapping() { + // Get preferences editor + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext()); + SharedPreferences.Editor editor = preferences.edit(); + + // Try remove all possible keys we wrote for this setting + String oldKey = preferences.getString(getReverseKey(), ""); + if (!oldKey.equals("")) { + editor.remove(getKey()); // Used for ui text + editor.remove(oldKey); // Used for button mapping + editor.remove(oldKey + "_GuestOrientation"); // Used for axis orientation + editor.remove(oldKey + "_GuestButton"); // Used for axis button + } + + // Apply changes + editor.apply(); + } + + /** + * Helper function to get the settings key for an gamepad button. + */ + public static String getInputButtonKey(int keyCode) { + return INPUT_MAPPING_PREFIX + "_Button_" + keyCode; + } + + /** + * Helper function to get the settings key for an gamepad axis. + */ + public static String getInputAxisKey(int axis) { + return INPUT_MAPPING_PREFIX + "_HostAxis_" + axis; + } + + /** + * Helper function to get the settings key for an gamepad axis button (stick or trigger). + */ + public static String getInputAxisButtonKey(int axis) { + return getInputAxisKey(axis) + "_GuestButton"; + } + + /** + * Helper function to get the settings key for an gamepad axis orientation. + */ + public static String getInputAxisOrientationKey(int axis) { + return getInputAxisKey(axis) + "_GuestOrientation"; + } + + /** + * Helper function to write a gamepad button mapping for the setting. + */ + private void WriteButtonMapping(String key) { + // Get preferences editor + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext()); + SharedPreferences.Editor editor = preferences.edit(); + + // Remove mapping for another setting using this input + int oldButtonCode = preferences.getInt(key, -1); + if (oldButtonCode != -1) { + String oldKey = getButtonKey(oldButtonCode); + editor.remove(oldKey); // Only need to remove UI text setting, others will be overwritten + } + + // Cleanup old mapping for this setting + removeOldMapping(); + + // Write new mapping + editor.putInt(key, getButtonCode()); + + // Write next reverse mapping for future cleanup + editor.putString(getReverseKey(), key); + + // Apply changes + editor.apply(); + } + + /** + * Helper function to write a gamepad axis mapping for the setting. + */ + private void WriteAxisMapping(int axis, int value) { + // Get preferences editor + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext()); + SharedPreferences.Editor editor = preferences.edit(); + + // Cleanup old mapping + removeOldMapping(); + + // Write new mapping + editor.putInt(getInputAxisOrientationKey(axis), IsHorizontalOrientation() ? 0 : 1); + editor.putInt(getInputAxisButtonKey(axis), value); + + // Write next reverse mapping for future cleanup + editor.putString(getReverseKey(), getInputAxisKey(axis)); + + // Apply changes + editor.apply(); + } + + /** + * Saves the provided key input setting as an Android preference. + * + * @param keyEvent KeyEvent of this key press. + */ + public void onKeyInput(KeyEvent keyEvent) { + if (!IsButtonMappingSupported()) { + Toast.makeText(CitraApplication.getAppContext(), R.string.input_message_analog_only, Toast.LENGTH_LONG).show(); + return; + } + + InputDevice device = keyEvent.getDevice(); + + WriteButtonMapping(getInputButtonKey(keyEvent.getKeyCode())); + + String uiString = device.getName() + ": Button " + keyEvent.getKeyCode(); + setUiString(uiString); + } + + /** + * Saves the provided motion input setting as an Android preference. + * + * @param device InputDevice from which the input event originated. + * @param motionRange MotionRange of the movement + * @param axisDir Either '-' or '+' (currently unused) + */ + public void onMotionInput(InputDevice device, InputDevice.MotionRange motionRange, + char axisDir) { + if (!IsAxisMappingSupported()) { + Toast.makeText(CitraApplication.getAppContext(), R.string.input_message_button_only, Toast.LENGTH_LONG).show(); + return; + } + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext()); + SharedPreferences.Editor editor = preferences.edit(); + + int button; + if (IsCirclePad()) { + button = NativeLibrary.ButtonType.STICK_LEFT; + } else if (IsCStick()) { + button = NativeLibrary.ButtonType.STICK_C; + } else if (IsDPad()) { + button = NativeLibrary.ButtonType.DPAD; + } else { + button = getButtonCode(); + } + + WriteAxisMapping(motionRange.getAxis(), button); + + String uiString = device.getName() + ": Axis " + motionRange.getAxis(); + setUiString(uiString); + + editor.apply(); + } + + /** + * Sets the string to use in the configuration UI for the gamepad input. + */ + private StringSetting setUiString(String ui) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext()); + SharedPreferences.Editor editor = preferences.edit(); + + if (getSetting() == null) { + StringSetting setting = new StringSetting(getKey(), getSection(), ""); + setSetting(setting); + + editor.putString(setting.getKey(), ui); + editor.apply(); + + return setting; + } else { + StringSetting setting = (StringSetting) getSetting(); + + editor.putString(setting.getKey(), ui); + editor.apply(); + + return null; + } + } + + @Override + public int getType() { + return TYPE_INPUT_BINDING; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/PremiumHeader.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/PremiumHeader.java new file mode 100644 index 000000000..2b1793d3e --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/PremiumHeader.java @@ -0,0 +1,12 @@ +package org.citra.citra_emu.features.settings.model.view; + +public final class PremiumHeader extends SettingsItem { + public PremiumHeader() { + super(null, null, null, 0, 0); + } + + @Override + public int getType() { + return SettingsItem.TYPE_PREMIUM; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/PremiumSingleChoiceSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/PremiumSingleChoiceSetting.java new file mode 100644 index 000000000..c0560d2dc --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/PremiumSingleChoiceSetting.java @@ -0,0 +1,59 @@ +package org.citra.citra_emu.features.settings.model.view; + +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import org.citra.citra_emu.CitraApplication; +import org.citra.citra_emu.R; +import org.citra.citra_emu.features.settings.model.Setting; +import org.citra.citra_emu.features.settings.ui.SettingsFragmentView; + +public final class PremiumSingleChoiceSetting extends SettingsItem { + private int mDefaultValue; + + private int mChoicesId; + private int mValuesId; + private SettingsFragmentView mView; + + private static SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext()); + + public PremiumSingleChoiceSetting(String key, String section, int titleId, int descriptionId, + int choicesId, int valuesId, int defaultValue, Setting setting, SettingsFragmentView view) { + super(key, section, setting, titleId, descriptionId); + mValuesId = valuesId; + mChoicesId = choicesId; + mDefaultValue = defaultValue; + mView = view; + } + + public int getChoicesId() { + return mChoicesId; + } + + public int getValuesId() { + return mValuesId; + } + + public int getSelectedValue() { + return mPreferences.getInt(getKey(), mDefaultValue); + } + + /** + * Write a value to the backing int. If that int was previously null, + * initializes a new one and returns it, so it can be added to the Hashmap. + * + * @param selection New value of the int. + * @return null if overwritten successfully otherwise; a newly created IntSetting. + */ + public void setSelectedValue(int selection) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(getKey(), selection); + editor.apply(); + mView.showToastMessage(CitraApplication.getAppContext().getString(R.string.design_updated), false); + } + + @Override + public int getType() { + return TYPE_SINGLE_CHOICE; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.java new file mode 100644 index 000000000..305352022 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.java @@ -0,0 +1,107 @@ +package org.citra.citra_emu.features.settings.model.view; + +import org.citra.citra_emu.features.settings.model.Setting; +import org.citra.citra_emu.features.settings.model.Settings; +import org.citra.citra_emu.features.settings.ui.SettingsAdapter; + +/** + * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. + * Each one corresponds to a {@link Setting} object, so this class's subclasses + * should vaguely correspond to those subclasses. There are a few with multiple analogues + * and a few with none (Headers, for example, do not correspond to anything in the ini + * file.) + */ +public abstract class SettingsItem { + public static final int TYPE_HEADER = 0; + public static final int TYPE_CHECKBOX = 1; + public static final int TYPE_SINGLE_CHOICE = 2; + public static final int TYPE_SLIDER = 3; + public static final int TYPE_SUBMENU = 4; + public static final int TYPE_INPUT_BINDING = 5; + public static final int TYPE_STRING_SINGLE_CHOICE = 6; + public static final int TYPE_DATETIME_SETTING = 7; + public static final int TYPE_PREMIUM = 8; + + private String mKey; + private String mSection; + + private Setting mSetting; + + private int mNameId; + private int mDescriptionId; + private boolean mIsPremium; + + /** + * Base constructor. Takes a key / section name in case the third parameter, the Setting, + * is null; in which case, one can be constructed and saved using the key / section. + * + * @param key Identifier for the Setting represented by this Item. + * @param section Section to which the Setting belongs. + * @param setting A possibly-null backing Setting, to be modified on UI events. + * @param nameId Resource ID for a text string to be displayed as this setting's name. + * @param descriptionId Resource ID for a text string to be displayed as this setting's description. + */ + public SettingsItem(String key, String section, Setting setting, int nameId, + int descriptionId) { + mKey = key; + mSection = section; + mSetting = setting; + mNameId = nameId; + mDescriptionId = descriptionId; + mIsPremium = (section == Settings.SECTION_PREMIUM); + } + + /** + * @return The identifier for the backing Setting. + */ + public String getKey() { + return mKey; + } + + /** + * @return The header under which the backing Setting belongs. + */ + public String getSection() { + return mSection; + } + + /** + * @return The backing Setting, possibly null. + */ + public Setting getSetting() { + return mSetting; + } + + /** + * Replace the backing setting with a new one. Generally used in cases where + * the backing setting is null. + * + * @param setting A non-null Setting. + */ + public void setSetting(Setting setting) { + mSetting = setting; + } + + /** + * @return A resource ID for a text string representing this Setting's name. + */ + public int getNameId() { + return mNameId; + } + + public int getDescriptionId() { + return mDescriptionId; + } + + public boolean isPremium() { + return mIsPremium; + } + + /** + * Used by {@link SettingsAdapter}'s onCreateViewHolder() + * method to determine which type of ViewHolder should be created. + * + * @return An integer (ideally, one of the constants defined in this file) + */ + public abstract int getType(); +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SingleChoiceSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SingleChoiceSetting.java new file mode 100644 index 000000000..ee9d225d6 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SingleChoiceSetting.java @@ -0,0 +1,60 @@ +package org.citra.citra_emu.features.settings.model.view; + +import org.citra.citra_emu.features.settings.model.IntSetting; +import org.citra.citra_emu.features.settings.model.Setting; + +public final class SingleChoiceSetting extends SettingsItem { + private int mDefaultValue; + + private int mChoicesId; + private int mValuesId; + + public SingleChoiceSetting(String key, String section, int titleId, int descriptionId, + int choicesId, int valuesId, int defaultValue, Setting setting) { + super(key, section, setting, titleId, descriptionId); + mValuesId = valuesId; + mChoicesId = choicesId; + mDefaultValue = defaultValue; + } + + public int getChoicesId() { + return mChoicesId; + } + + public int getValuesId() { + return mValuesId; + } + + public int getSelectedValue() { + if (getSetting() != null) { + IntSetting setting = (IntSetting) getSetting(); + return setting.getValue(); + } else { + return mDefaultValue; + } + } + + /** + * Write a value to the backing int. If that int was previously null, + * initializes a new one and returns it, so it can be added to the Hashmap. + * + * @param selection New value of the int. + * @return null if overwritten successfully otherwise; a newly created IntSetting. + */ + public IntSetting setSelectedValue(int selection) { + if (getSetting() == null) { + IntSetting setting = new IntSetting(getKey(), getSection(), selection); + setSetting(setting); + return setting; + } else { + IntSetting setting = (IntSetting) getSetting(); + setting.setValue(selection); + return null; + } + } + + @Override + public int getType() { + return TYPE_SINGLE_CHOICE; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.java new file mode 100644 index 000000000..551b13f99 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SliderSetting.java @@ -0,0 +1,101 @@ +package org.citra.citra_emu.features.settings.model.view; + +import org.citra.citra_emu.features.settings.model.FloatSetting; +import org.citra.citra_emu.features.settings.model.IntSetting; +import org.citra.citra_emu.features.settings.model.Setting; +import org.citra.citra_emu.utils.Log; + +public final class SliderSetting extends SettingsItem { + private int mMin; + private int mMax; + private int mDefaultValue; + + private String mUnits; + + public SliderSetting(String key, String section, int titleId, int descriptionId, + int min, int max, String units, int defaultValue, Setting setting) { + super(key, section, setting, titleId, descriptionId); + mMin = min; + mMax = max; + mUnits = units; + mDefaultValue = defaultValue; + } + + public int getMin() { + return mMin; + } + + public int getMax() { + return mMax; + } + + public int getDefaultValue() { + return mDefaultValue; + } + + public int getSelectedValue() { + Setting setting = getSetting(); + + if (setting == null) { + return mDefaultValue; + } + + if (setting instanceof IntSetting) { + IntSetting intSetting = (IntSetting) setting; + return intSetting.getValue(); + } else if (setting instanceof FloatSetting) { + FloatSetting floatSetting = (FloatSetting) setting; + return Math.round(floatSetting.getValue()); + } else { + Log.error("[SliderSetting] Error casting setting type."); + return -1; + } + } + + /** + * Write a value to the backing int. If that int was previously null, + * initializes a new one and returns it, so it can be added to the Hashmap. + * + * @param selection New value of the int. + * @return null if overwritten successfully otherwise; a newly created IntSetting. + */ + public IntSetting setSelectedValue(int selection) { + if (getSetting() == null) { + IntSetting setting = new IntSetting(getKey(), getSection(), selection); + setSetting(setting); + return setting; + } else { + IntSetting setting = (IntSetting) getSetting(); + setting.setValue(selection); + return null; + } + } + + /** + * Write a value to the backing float. If that float was previously null, + * initializes a new one and returns it, so it can be added to the Hashmap. + * + * @param selection New value of the float. + * @return null if overwritten successfully otherwise; a newly created FloatSetting. + */ + public FloatSetting setSelectedValue(float selection) { + if (getSetting() == null) { + FloatSetting setting = new FloatSetting(getKey(), getSection(), selection); + setSetting(setting); + return setting; + } else { + FloatSetting setting = (FloatSetting) getSetting(); + setting.setValue(selection); + return null; + } + } + + public String getUnits() { + return mUnits; + } + + @Override + public int getType() { + return TYPE_SLIDER; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringSingleChoiceSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringSingleChoiceSetting.java new file mode 100644 index 000000000..057145d9d --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/StringSingleChoiceSetting.java @@ -0,0 +1,82 @@ +package org.citra.citra_emu.features.settings.model.view; + +import org.citra.citra_emu.features.settings.model.Setting; +import org.citra.citra_emu.features.settings.model.StringSetting; + +public class StringSingleChoiceSetting extends SettingsItem { + private String mDefaultValue; + + private String[] mChoicesId; + private String[] mValuesId; + + public StringSingleChoiceSetting(String key, String section, int titleId, int descriptionId, + String[] choicesId, String[] valuesId, String defaultValue, Setting setting) { + super(key, section, setting, titleId, descriptionId); + mValuesId = valuesId; + mChoicesId = choicesId; + mDefaultValue = defaultValue; + } + + public String[] getChoicesId() { + return mChoicesId; + } + + public String[] getValuesId() { + return mValuesId; + } + + public String getValueAt(int index) { + if (mValuesId == null) + return null; + + if (index >= 0 && index < mValuesId.length) { + return mValuesId[index]; + } + + return ""; + } + + public String getSelectedValue() { + if (getSetting() != null) { + StringSetting setting = (StringSetting) getSetting(); + return setting.getValue(); + } else { + return mDefaultValue; + } + } + + public int getSelectValueIndex() { + String selectedValue = getSelectedValue(); + for (int i = 0; i < mValuesId.length; i++) { + if (mValuesId[i].equals(selectedValue)) { + return i; + } + } + + return -1; + } + + /** + * Write a value to the backing int. If that int was previously null, + * initializes a new one and returns it, so it can be added to the Hashmap. + * + * @param selection New value of the int. + * @return null if overwritten successfully otherwise; a newly created IntSetting. + */ + public StringSetting setSelectedValue(String selection) { + if (getSetting() == null) { + StringSetting setting = new StringSetting(getKey(), getSection(), selection); + setSetting(setting); + return setting; + } else { + StringSetting setting = (StringSetting) getSetting(); + setting.setValue(selection); + return null; + } + } + + @Override + public int getType() { + return TYPE_STRING_SINGLE_CHOICE; + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SubmenuSetting.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SubmenuSetting.java new file mode 100644 index 000000000..9d44a923f --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SubmenuSetting.java @@ -0,0 +1,21 @@ +package org.citra.citra_emu.features.settings.model.view; + +import org.citra.citra_emu.features.settings.model.Setting; + +public final class SubmenuSetting extends SettingsItem { + private String mMenuKey; + + public SubmenuSetting(String key, Setting setting, int titleId, int descriptionId, String menuKey) { + super(key, null, setting, titleId, descriptionId); + mMenuKey = menuKey; + } + + public String getMenuKey() { + return mMenuKey; + } + + @Override + public int getType() { + return TYPE_SUBMENU; + } +} |