diff --git a/Source/Android/app/src/arm_64/res/values/arrays.xml b/Source/Android/app/src/arm_64/res/values/arrays.xml index 8f2ed0edff..fb98aa63da 100644 --- a/Source/Android/app/src/arm_64/res/values/arrays.xml +++ b/Source/Android/app/src/arm_64/res/values/arrays.xml @@ -9,10 +9,10 @@ @string/cached_interpreter @string/jit_arm64_recompiler - + 0 5 4 - + \ No newline at end of file diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml index a6a7ecc888..57a0e62abd 100644 --- a/Source/Android/app/src/main/AndroidManifest.xml +++ b/Source/Android/app/src/main/AndroidManifest.xml @@ -56,9 +56,9 @@ android:label="@string/add_directory_title"/> + android:label="@string/grid_menu_core_settings"/> - - shader_names = new ArrayList(); - List shader_values = new ArrayList(); - - // Disabled option - shader_names.add("Disabled"); - shader_values.add(""); - - // TODO Since shaders are included with the APK, we know what they are at build-time. We should - // TODO be able to run this logic somehow at build-time and not rely on the device doing it. - - File shaders_folder = new File(Environment.getExternalStorageDirectory() + File.separator + "dolphin-emu" + File.separator + "Shaders"); - if (shaders_folder.exists()) - { - File[] shaders = shaders_folder.listFiles(); - for (File file : shaders) - { - if (file.isFile()) - { - String filename = file.getName(); - if (filename.endsWith(".glsl")) - { - // Strip the extension and put it in to the list - shader_names.add(filename.substring(0, filename.lastIndexOf('.'))); - shader_values.add(filename.substring(0, filename.lastIndexOf('.'))); - } - } - } - } - - final ListPreference shader_preference = (ListPreference) findPreference("postProcessingShader"); - shader_preference.setEntries(shader_names.toArray(new CharSequence[shader_names.size()])); - shader_preference.setEntryValues(shader_values.toArray(new CharSequence[shader_values.size()])); - - // - // Disable all options if Software Rendering is used. - // - // Note that the numeric value in 'getPreference()' - // denotes the placement on the UI. So if more elements are - // added to the video settings, these may need to change. - // - mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); - - if (mVideoBackendPreference.getValue().equals("Software Renderer")) - { - findPreference("enhancements").setEnabled(false); - findPreference("hacks").setEnabled(false); - findPreference("showFPS").setEnabled(false); - } - else if (mVideoBackendPreference.getValue().equals("OGL")) - { - findPreference("enhancements").setEnabled(true); - findPreference("hacks").setEnabled(true); - findPreference("showFPS").setEnabled(true); - - // Check if we support stereo - // If we support desktop GL then we must support at least OpenGL 3.2 - // If we only support OpenGLES then we need both OpenGLES 3.1 and AEP - if ((mEglHelper.supportsOpenGL() && mEglHelper.GetVersion() >= 320) || - (mEglHelper.supportsGLES3() && mEglHelper.GetVersion() >= 310 && mEglHelper.SupportsExtension("GL_ANDROID_extension_pack_es31a"))) - findPreference("StereoscopyScreen").setEnabled(true); - else - findPreference("StereoscopyScreen").setEnabled(false); - } - - // Also set a listener, so that if someone changes the video backend, it will disable - // the video settings, upon the user choosing "Software Rendering". - mPreferences.registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences preferences, String key) - { - if (key.equals("gpuPref")) - { - if (preferences.getString(key, "Software Renderer").equals("Software Renderer")) - { - findPreference("enhancements").setEnabled(false); - findPreference("hacks").setEnabled(false); - findPreference("showFPS").setEnabled(false); - } - else if (preferences.getString(key, "Software Renderer").equals("OGL")) - { - findPreference("enhancements").setEnabled(true); - findPreference("hacks").setEnabled(true); - findPreference("showFPS").setEnabled(true); - } - } - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/BooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/BooleanSetting.java new file mode 100644 index 0000000000..478321bb8e --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/BooleanSetting.java @@ -0,0 +1,28 @@ +package org.dolphinemu.dolphinemu.model.settings; + +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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/FloatSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/FloatSetting.java new file mode 100644 index 0000000000..af78fac15d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/FloatSetting.java @@ -0,0 +1,28 @@ +package org.dolphinemu.dolphinemu.model.settings; + +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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/IntSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/IntSetting.java new file mode 100644 index 0000000000..334abc86b2 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/IntSetting.java @@ -0,0 +1,28 @@ +package org.dolphinemu.dolphinemu.model.settings; + +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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/Setting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/Setting.java new file mode 100644 index 0000000000..8ede2bbe07 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/Setting.java @@ -0,0 +1,48 @@ +package org.dolphinemu.dolphinemu.model.settings; + +/** + * Abstraction for a setting item as read from / written to Dolphin'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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/SettingSection.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/SettingSection.java new file mode 100644 index 0000000000..9f082f6e9d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/SettingSection.java @@ -0,0 +1,56 @@ +package org.dolphinemu.dolphinemu.model.settings; + +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 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 key The key where the Setting will be inserted. + * @param setting The Setting to be inserted. + */ + public void putSetting(String key, Setting setting) + { + mSettings.put(key, 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 getSettings() + { + return mSettings; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/StringSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/StringSetting.java new file mode 100644 index 0000000000..257cb8a36d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/StringSetting.java @@ -0,0 +1,28 @@ +package org.dolphinemu.dolphinemu.model.settings; + +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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/CheckBoxSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/CheckBoxSetting.java new file mode 100644 index 0000000000..adc19f8c30 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/CheckBoxSetting.java @@ -0,0 +1,56 @@ +package org.dolphinemu.dolphinemu.model.settings.view; + + +import org.dolphinemu.dolphinemu.model.settings.BooleanSetting; +import org.dolphinemu.dolphinemu.model.settings.Setting; + +public final class CheckBoxSetting extends SettingsItem +{ + private boolean mDefaultValue; + + public CheckBoxSetting(String key, String section, int titleId, int descriptionId, boolean defaultValue, Setting setting) + { + super(key, section, setting, titleId, descriptionId); + mDefaultValue = defaultValue; + } + + public boolean isChecked() + { + if (getSetting() == null) + { + return mDefaultValue; + } + + BooleanSetting setting = (BooleanSetting) getSetting(); + return setting.getValue(); + } + + /** + * 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 BooleanSetting setChecked(boolean checked) + { + if (getSetting() == null) + { + BooleanSetting setting = new BooleanSetting(getKey(), getSection(), checked); + setSetting(setting); + return setting; + } + else + { + BooleanSetting setting = (BooleanSetting) getSetting(); + setting.setValue(checked); + return null; + } + } + + @Override + public int getType() + { + return TYPE_CHECKBOX; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/HeaderSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/HeaderSetting.java new file mode 100644 index 0000000000..2d5744c555 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/HeaderSetting.java @@ -0,0 +1,18 @@ +package org.dolphinemu.dolphinemu.model.settings.view; + + +import org.dolphinemu.dolphinemu.model.settings.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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SettingsItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SettingsItem.java new file mode 100644 index 0000000000..4ec62717b7 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SettingsItem.java @@ -0,0 +1,106 @@ +package org.dolphinemu.dolphinemu.model.settings.view; + +import org.dolphinemu.dolphinemu.model.settings.Setting; + +/** + * 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; + + private String mKey; + private String mSection; + + private Setting mSetting; + + private int mNameId; + private int mDescriptionId; + + /** + * 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; + } + + /** + * + * @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; + } + + /** + * Used by {@link org.dolphinemu.dolphinemu.ui.settings.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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SingleChoiceSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SingleChoiceSetting.java new file mode 100644 index 0000000000..07880587c2 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SingleChoiceSetting.java @@ -0,0 +1,73 @@ +package org.dolphinemu.dolphinemu.model.settings.view; + + +import org.dolphinemu.dolphinemu.model.settings.IntSetting; +import org.dolphinemu.dolphinemu.model.settings.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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SliderSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SliderSetting.java new file mode 100644 index 0000000000..0dbb39b521 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SliderSetting.java @@ -0,0 +1,118 @@ +package org.dolphinemu.dolphinemu.model.settings.view; + +import org.dolphinemu.dolphinemu.model.settings.FloatSetting; +import org.dolphinemu.dolphinemu.model.settings.IntSetting; +import org.dolphinemu.dolphinemu.model.settings.Setting; +import org.dolphinemu.dolphinemu.utils.Log; +import org.dolphinemu.dolphinemu.utils.SettingsFile; + +public final class SliderSetting extends SettingsItem +{ + private int mMax; + private int mDefaultValue; + + private String mUnits; + + public SliderSetting(String key, String section, int titleId, int descriptionId, int max, String units, int defaultValue, Setting setting) + { + super(key, section, setting, titleId, descriptionId); + mMax = max; + mUnits = units; + mDefaultValue = defaultValue; + } + + public int getMax() + { + return mMax; + } + + 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; + if (floatSetting.getKey().equals(SettingsFile.KEY_OVERCLOCK_PERCENT)) + { + return Math.round(floatSetting.getValue() * 100); + } + else + { + 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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SubmenuSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SubmenuSetting.java new file mode 100644 index 0000000000..32db709b37 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/settings/view/SubmenuSetting.java @@ -0,0 +1,25 @@ +package org.dolphinemu.dolphinemu.model.settings.view; + +import org.dolphinemu.dolphinemu.model.settings.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; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/AssetCopyService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/AssetCopyService.java index 6004c88c5e..a93e9c501b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/AssetCopyService.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/AssetCopyService.java @@ -13,7 +13,6 @@ import android.preference.PreferenceManager; import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.utils.Log; -import org.dolphinemu.dolphinemu.utils.UserPreferences; import java.io.File; import java.io.FileOutputStream; @@ -61,10 +60,6 @@ public final class AssetCopyService extends IntentService copyAsset("GCPadNew.ini", ConfigDir + File.separator + "GCPadNew.ini"); copyAsset("WiimoteNew.ini", ConfigDir + File.separator + "WiimoteNew.ini"); - // Load the configuration keys set in the Dolphin ini and gfx ini files - // into the application's shared preferences. - UserPreferences.LoadIniToPrefs(this); - // Record the fact that we've done this before, so we don't do it on every launch. SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences.Editor editor = preferences.edit(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SettingsSaveService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SettingsSaveService.java deleted file mode 100644 index 5be3f4d27c..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SettingsSaveService.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.dolphinemu.dolphinemu.services; - -import android.app.IntentService; -import android.content.Intent; - -import org.dolphinemu.dolphinemu.utils.Log; -import org.dolphinemu.dolphinemu.utils.UserPreferences; - -/** - * IntentServices, unlike regular services, inherently run on a background thread. - * This IntentService saves all the options the user set in the Java-based UI into - * INI files the native code can read. - */ -public final class SettingsSaveService extends IntentService -{ - public SettingsSaveService() - { - super("SettingsSaveService"); - } - - @Override - protected void onHandleIntent(Intent intent) - { - Log.verbose("[SettingsSaveService] Saving settings to INI files..."); - UserPreferences.SavePrefsToIni(this); - Log.verbose("[SettingsSaveService] Save successful."); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/DividerItemDecoration.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/DividerItemDecoration.java new file mode 100644 index 0000000000..fcd0befdc2 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/DividerItemDecoration.java @@ -0,0 +1,157 @@ +package org.dolphinemu.dolphinemu.ui; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.View; + +/** + * Implementation from: + * https://gist.github.com/lapastillaroja/858caf1a82791b6c1a36 + */ +public final class DividerItemDecoration extends RecyclerView.ItemDecoration +{ + + private Drawable mDivider; + private boolean mShowFirstDivider = false; + private boolean mShowLastDivider = false; + + + public DividerItemDecoration(Context context, AttributeSet attrs) + { + final TypedArray a = context + .obtainStyledAttributes(attrs, new int[]{android.R.attr.listDivider}); + mDivider = a.getDrawable(0); + a.recycle(); + } + + public DividerItemDecoration(Context context, AttributeSet attrs, boolean showFirstDivider, + boolean showLastDivider) + { + this(context, attrs); + mShowFirstDivider = showFirstDivider; + mShowLastDivider = showLastDivider; + } + + public DividerItemDecoration(Drawable divider) + { + mDivider = divider; + } + + public DividerItemDecoration(Drawable divider, boolean showFirstDivider, + boolean showLastDivider) + { + this(divider); + mShowFirstDivider = showFirstDivider; + mShowLastDivider = showLastDivider; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) + { + super.getItemOffsets(outRect, view, parent, state); + if (mDivider == null) + { + return; + } + if (parent.getChildPosition(view) < 1) + { + return; + } + + if (getOrientation(parent) == LinearLayoutManager.VERTICAL) + { + outRect.top = mDivider.getIntrinsicHeight(); + } + else + { + outRect.left = mDivider.getIntrinsicWidth(); + } + } + + @Override + public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) + { + if (mDivider == null) + { + super.onDrawOver(c, parent, state); + return; + } + + // Initialization needed to avoid compiler warning + int left = 0, right = 0, top = 0, bottom = 0, size; + int orientation = getOrientation(parent); + int childCount = parent.getChildCount(); + + if (orientation == LinearLayoutManager.VERTICAL) + { + size = mDivider.getIntrinsicHeight(); + left = parent.getPaddingLeft(); + right = parent.getWidth() - parent.getPaddingRight(); + } + else + { //horizontal + size = mDivider.getIntrinsicWidth(); + top = parent.getPaddingTop(); + bottom = parent.getHeight() - parent.getPaddingBottom(); + } + + for (int i = mShowFirstDivider ? 0 : 1; i < childCount; i++) + { + View child = parent.getChildAt(i); + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); + + if (orientation == LinearLayoutManager.VERTICAL) + { + top = child.getTop() - params.topMargin; + bottom = top + size; + } + else + { //horizontal + left = child.getLeft() - params.leftMargin; + right = left + size; + } + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + + // show last divider + if (mShowLastDivider && childCount > 0) + { + View child = parent.getChildAt(childCount - 1); + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); + if (orientation == LinearLayoutManager.VERTICAL) + { + top = child.getBottom() + params.bottomMargin; + bottom = top + size; + } + else + { // horizontal + left = child.getRight() + params.rightMargin; + right = left + size; + } + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + private int getOrientation(RecyclerView parent) + { + if (parent.getLayoutManager() instanceof LinearLayoutManager) + { + LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager(); + return layoutManager.getOrientation(); + } + else + { + throw new IllegalStateException( + "DividerItemDecoration can only be used with a LinearLayoutManager."); + } + } +} \ No newline at end of file diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java index e112899201..57923e0e0e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java @@ -16,10 +16,10 @@ import android.view.View; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.activities.AddDirectoryActivity; -import org.dolphinemu.dolphinemu.activities.SettingsActivity; import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter; import org.dolphinemu.dolphinemu.model.GameProvider; import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView; +import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity; import org.dolphinemu.dolphinemu.utils.StartupHandler; /** @@ -115,9 +115,9 @@ public final class MainActivity extends AppCompatActivity implements MainView } @Override - public void launchSettingsActivity() + public void launchSettingsActivity(String menuTag) { - SettingsActivity.launch(this); + SettingsActivity.launch(this, menuTag); } @Override diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java index 97b7a39f25..c1052fc42a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java @@ -7,6 +7,7 @@ import org.dolphinemu.dolphinemu.DolphinApplication; import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.model.GameDatabase; +import org.dolphinemu.dolphinemu.utils.SettingsFile; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; @@ -41,8 +42,12 @@ public final class MainPresenter { switch (itemId) { - case R.id.menu_settings: - mView.launchSettingsActivity(); + case R.id.menu_settings_core: + mView.launchSettingsActivity(SettingsFile.FILE_NAME_DOLPHIN); + return true; + + case R.id.menu_settings_video: + mView.launchSettingsActivity(SettingsFile.FILE_NAME_GFX); return true; case R.id.menu_refresh: diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java index 1d286bebf3..12c8ae832c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java @@ -32,7 +32,7 @@ public interface MainView void refreshFragmentScreenshot(int fragmentPosition); - void launchSettingsActivity(); + void launchSettingsActivity(String menuTag); void launchFileListActivity(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java index 38ab2eb4b4..95e4d04746 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java @@ -20,11 +20,12 @@ import android.support.v17.leanback.widget.RowPresenter; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.activities.AddDirectoryActivity; import org.dolphinemu.dolphinemu.activities.EmulationActivity; -import org.dolphinemu.dolphinemu.activities.SettingsActivity; import org.dolphinemu.dolphinemu.adapters.GameRowPresenter; import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter; import org.dolphinemu.dolphinemu.model.Game; import org.dolphinemu.dolphinemu.model.TvSettingsItem; +import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity; +import org.dolphinemu.dolphinemu.utils.SettingsFile; import org.dolphinemu.dolphinemu.utils.StartupHandler; import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder; @@ -112,9 +113,9 @@ public final class TvMainActivity extends Activity implements MainView } @Override - public void launchSettingsActivity() + public void launchSettingsActivity(String menuTag) { - SettingsActivity.launch(this); + SettingsActivity.launch(this, SettingsFile.FILE_NAME_DOLPHIN); } @Override @@ -229,9 +230,13 @@ public final class TvMainActivity extends Activity implements MainView R.drawable.ic_refresh_tv, R.string.grid_menu_refresh)); - rowItems.add(new TvSettingsItem(R.id.menu_settings, - R.drawable.ic_settings_tv, - R.string.grid_menu_settings)); + rowItems.add(new TvSettingsItem(R.id.menu_settings_core, + R.drawable.ic_settings_core_tv, + R.string.grid_menu_core_settings)); + + rowItems.add(new TvSettingsItem(R.id.menu_settings_video, + R.drawable.ic_settings_graphics_tv, + R.string.grid_menu_core_settings)); rowItems.add(new TvSettingsItem(R.id.button_add_directory, R.drawable.ic_add_tv, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsActivity.java new file mode 100644 index 0000000000..7cd744aff2 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsActivity.java @@ -0,0 +1,106 @@ +package org.dolphinemu.dolphinemu.ui.settings; + + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.AppCompatActivity; +import android.widget.Toast; + +import org.dolphinemu.dolphinemu.BuildConfig; +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.settings.SettingSection; + +import java.util.HashMap; + +public final class SettingsActivity extends AppCompatActivity implements SettingsActivityView +{ + private SettingsActivityPresenter mPresenter = new SettingsActivityPresenter(this); + + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_settings); + + Intent launcher = getIntent(); + String filename = launcher.getStringExtra(ARGUMENT_FILE_NAME); + + mPresenter.onCreate(savedInstanceState, filename); + } + + /** + * If this is called, the user has left the settings screen (potentially through the + * home button) and will expect their changes to be persisted. So we kick off an + * IntentService which will do so on a background thread. + */ + @Override + protected void onStop() + { + super.onStop(); + + mPresenter.onStop(isFinishing()); + } + + @Override + public void showSettingsFragment(String menuTag, boolean addToStack) + { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction() + .replace(R.id.frame_content, SettingsFragment.newInstance(menuTag), SettingsFragment.FRAGMENT_TAG); + + if (addToStack) + { + transaction.addToBackStack(null); + } + + transaction.commit(); + } + + @Override + public HashMap getSettings() + { + return mPresenter.getSettings(); + } + + @Override + public void setSettings(HashMap settings) + { + mPresenter.setSettings(settings); + } + + @Override + public void onSettingsFileLoaded(HashMap settings) + { + SettingsFragmentView fragment = getFragment(); + + if (fragment != null) + { + fragment.onSettingsFileLoaded(settings); + } + } + + @Override + public void showToastMessage(String message) + { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + + private SettingsFragment getFragment() + { + return (SettingsFragment) getSupportFragmentManager().findFragmentByTag(SettingsFragment.FRAGMENT_TAG); + } + + public static final String ARGUMENT_FILE_NAME = BuildConfig.APPLICATION_ID + ".file_name"; + + public static void launch(Context context, String menuTag) + { + Intent settings = new Intent(context, SettingsActivity.class); + + settings.putExtra(ARGUMENT_FILE_NAME, menuTag); + + context.startActivity(settings); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsActivityPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsActivityPresenter.java new file mode 100644 index 0000000000..aac7e8e2ed --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsActivityPresenter.java @@ -0,0 +1,97 @@ +package org.dolphinemu.dolphinemu.ui.settings; + + +import android.os.Bundle; + +import org.dolphinemu.dolphinemu.model.settings.SettingSection; +import org.dolphinemu.dolphinemu.utils.Log; +import org.dolphinemu.dolphinemu.utils.SettingsFile; + +import java.util.HashMap; + +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.schedulers.Schedulers; + +public final class SettingsActivityPresenter +{ + private SettingsActivityView mView; + + private String mFileName; + private HashMap mSettingsBySection; + + public SettingsActivityPresenter(SettingsActivityView view) + { + mView = view; + } + + public void onCreate(Bundle savedInstanceState, final String filename) + { + mFileName = filename; + + if (savedInstanceState == null) + { + SettingsFile.readFile(mFileName) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() + { + @Override + public void call(HashMap settingsBySection) + { + mSettingsBySection = settingsBySection; + mView.onSettingsFileLoaded(settingsBySection); + } + }, + new Action1() + { + @Override + public void call(Throwable throwable) + { + Log.error("[SettingsActivityPresenter] Error reading file " + filename + ".ini: "+ throwable.getMessage()); + mView.onSettingsFileLoaded(null); + } + }); + + mView.showSettingsFragment(mFileName, false); + } + } + + public void setSettings(HashMap settings) + { + mSettingsBySection = settings; + } + + public HashMap getSettings() + { + return mSettingsBySection; + } + + public void onStop(boolean finishing) + { + if (mSettingsBySection != null && finishing) + { + Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI..."); + SettingsFile.saveFile(mFileName, mSettingsBySection) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + new Action1() + { + @Override + public void call(Boolean aBoolean) + { + mView.showToastMessage("Saved successfully to " + mFileName + ".ini"); + } + }, + new Action1() + { + @Override + public void call(Throwable throwable) + { + mView.showToastMessage("Error saving " + mFileName + ".ini: " + throwable.getMessage()); + } + }); + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsActivityView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsActivityView.java new file mode 100644 index 0000000000..2409ed1221 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsActivityView.java @@ -0,0 +1,51 @@ +package org.dolphinemu.dolphinemu.ui.settings; + +import org.dolphinemu.dolphinemu.model.settings.SettingSection; + +import java.util.HashMap; + +/** + * Abstraction for the Activity that manages SettingsFragments. + */ +public interface SettingsActivityView +{ + /** + * Show a new SettingsFragment. + * + * @param menuTag Identifier for the settings group that should be displayed. + * @param addToStack Whether or not this fragment should replace a previous one. + */ + void showSettingsFragment(String menuTag, boolean addToStack); + + /** + * 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 + * read operation. + * + * @return A possibly null Hashmap of Settings. + */ + HashMap getSettings(); + + /** + * Used to provide the Activity with a Settings Hashmap if a Fragment already + * has one; for example, if a rotation occurs, the Fragment will not be killed, + * but the Activity will, so the Activity needs to have its Hashmap resupplied. + * + * @param settings The Fragment's Settings hashmap. + */ + void setSettings(HashMap settings); + + /** + * Called when an asynchronous load operation completes. + * + * @param settings The (possibly null) result of the load operation. + */ + void onSettingsFileLoaded(HashMap settings); + + /** + * Display a popup text message on screen. + * + * @param message The contents of the onscreen message. + */ + void showToastMessage(String message); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsAdapter.java new file mode 100644 index 0000000000..c83c578f06 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsAdapter.java @@ -0,0 +1,334 @@ +package org.dolphinemu.dolphinemu.ui.settings; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SeekBar; +import android.widget.TextView; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.settings.BooleanSetting; +import org.dolphinemu.dolphinemu.model.settings.FloatSetting; +import org.dolphinemu.dolphinemu.model.settings.IntSetting; +import org.dolphinemu.dolphinemu.model.settings.view.CheckBoxSetting; +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; +import org.dolphinemu.dolphinemu.model.settings.view.SingleChoiceSetting; +import org.dolphinemu.dolphinemu.model.settings.view.SliderSetting; +import org.dolphinemu.dolphinemu.model.settings.view.SubmenuSetting; +import org.dolphinemu.dolphinemu.ui.settings.viewholder.CheckBoxSettingViewHolder; +import org.dolphinemu.dolphinemu.ui.settings.viewholder.HeaderViewHolder; +import org.dolphinemu.dolphinemu.ui.settings.viewholder.SettingViewHolder; +import org.dolphinemu.dolphinemu.ui.settings.viewholder.SingleChoiceViewHolder; +import org.dolphinemu.dolphinemu.ui.settings.viewholder.SliderViewHolder; +import org.dolphinemu.dolphinemu.ui.settings.viewholder.SubmenuViewHolder; +import org.dolphinemu.dolphinemu.utils.Log; +import org.dolphinemu.dolphinemu.utils.SettingsFile; + +import java.util.ArrayList; + +public final class SettingsAdapter extends RecyclerView.Adapter + implements DialogInterface.OnClickListener, SeekBar.OnSeekBarChangeListener +{ + private SettingsFragmentView mView; + private Context mContext; + private ArrayList mSettings; + + private SettingsItem mClickedItem; + private int mSeekbarProgress; + + private AlertDialog mDialog; + private TextView mTextSliderValue; + + public SettingsAdapter(SettingsFragmentView view, Context context) + { + mView = view; + mContext = context; + } + + @Override + public SettingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) + { + View view; + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + + switch (viewType) + { + case SettingsItem.TYPE_HEADER: + view = inflater.inflate(R.layout.list_item_settings_header, parent, false); + return new HeaderViewHolder(view, this); + + case SettingsItem.TYPE_CHECKBOX: + view = inflater.inflate(R.layout.list_item_setting_checkbox, parent, false); + return new CheckBoxSettingViewHolder(view, this); + + case SettingsItem.TYPE_SINGLE_CHOICE: + view = inflater.inflate(R.layout.list_item_setting, parent, false); + return new SingleChoiceViewHolder(view, this); + + case SettingsItem.TYPE_SLIDER: + view = inflater.inflate(R.layout.list_item_setting, parent, false); + return new SliderViewHolder(view, this); + + case SettingsItem.TYPE_SUBMENU: + view = inflater.inflate(R.layout.list_item_setting, parent, false); + return new SubmenuViewHolder(view, this); + + default: + Log.error("[SettingsAdapter] Invalid view type: " + viewType); + return null; + } + } + + @Override + public void onBindViewHolder(SettingViewHolder holder, int position) + { + holder.bind(getItem(position)); + } + + private SettingsItem getItem(int position) + { + return mSettings.get(position); + } + + @Override + public int getItemCount() + { + if (mSettings != null) + { + return mSettings.size(); + } + else + { + return 0; + } + } + + @Override + public int getItemViewType(int position) + { + return getItem(position).getType(); + } + + public void setSettings(ArrayList settings) + { + mSettings = settings; + notifyDataSetChanged(); + } + + public void onBooleanClick(CheckBoxSetting item, int position, boolean checked) + { + BooleanSetting setting = item.setChecked(checked); + notifyItemChanged(position); + + if (setting != null) + { + mView.putSetting(setting); + } + } + + public void onSingleChoiceClick(SingleChoiceSetting item) + { + mClickedItem = item; + + int value = getSelectionForSingleChoiceValue(item); + + AlertDialog.Builder builder = new AlertDialog.Builder(mView.getActivity()); + + builder.setTitle(item.getNameId()); + builder.setSingleChoiceItems(item.getChoicesId(), value, this); + + mDialog = builder.show(); + } + + public void onSliderClick(SliderSetting item) + { + mClickedItem = item; + mSeekbarProgress = item.getSelectedValue(); + AlertDialog.Builder builder = new AlertDialog.Builder(mView.getActivity()); + + LayoutInflater inflater = LayoutInflater.from(mView.getActivity()); + View view = inflater.inflate(R.layout.dialog_seekbar, null); + + builder.setTitle(item.getNameId()); + builder.setView(view); + builder.setPositiveButton(R.string.dialog_seekbar_pos, this); + builder.setNegativeButton(R.string.dialog_seekbar_neg, this); + mDialog = builder.show(); + + mTextSliderValue = (TextView) view.findViewById(R.id.text_value); + mTextSliderValue.setText(String.valueOf(mSeekbarProgress)); + + TextView units = (TextView) view.findViewById(R.id.text_units); + units.setText(item.getUnits()); + + SeekBar seekbar = (SeekBar) view.findViewById(R.id.seekbar); + + seekbar.setMax(item.getMax()); + seekbar.setProgress(mSeekbarProgress); + + seekbar.setOnSeekBarChangeListener(this); + } + + public void onSubmenuClick(SubmenuSetting item) + { + mView.loadSubMenu(item.getMenuKey()); + } + + @Override + public void onClick(DialogInterface dialog, int which) + { + if (mClickedItem instanceof SingleChoiceSetting) + { + SingleChoiceSetting scSetting = (SingleChoiceSetting) mClickedItem; + + int value = getValueForSingleChoiceSelection(scSetting, which); + + // Get the backing Setting, which may be null (if for example it was missing from the file) + IntSetting setting = scSetting.setSelectedValue(value); + if (setting != null) + { + mView.putSetting(setting); + } + else + { + if (scSetting.getKey().equals(SettingsFile.KEY_XFB_METHOD)) + { + putXfbSetting(which); + } + } + + closeDialog(); + } + else if (mClickedItem instanceof SliderSetting) + { + SliderSetting sliderSetting = (SliderSetting) mClickedItem; + if (sliderSetting.getSetting() instanceof FloatSetting) + { + float value; + + if (sliderSetting.getKey().equals(SettingsFile.KEY_OVERCLOCK_PERCENT)) + { + value = mSeekbarProgress / 100.0f; + } + else + { + value = (float) mSeekbarProgress; + } + + FloatSetting setting = sliderSetting.setSelectedValue(value); + if (setting != null) + { + mView.putSetting(setting); + } + } + else + { + IntSetting setting = sliderSetting.setSelectedValue(mSeekbarProgress); + if (setting != null) + { + mView.putSetting(setting); + } + } + } + + mClickedItem = null; + mSeekbarProgress = -1; + } + + public void closeDialog() + { + if (mDialog != null) + { + mDialog.dismiss(); + mDialog = null; + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) + { + mSeekbarProgress = progress; + mTextSliderValue.setText(String.valueOf(mSeekbarProgress)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) + { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) + { + } + + private int getValueForSingleChoiceSelection(SingleChoiceSetting item, int which) + { + int valuesId = item.getValuesId(); + + if (valuesId > 0) + { + int[] valuesArray = mContext.getResources().getIntArray(valuesId); + return valuesArray[which]; + } + else + { + return which; + } + } + + private int getSelectionForSingleChoiceValue(SingleChoiceSetting item) + { + int value = item.getSelectedValue(); + int valuesId = item.getValuesId(); + + if (valuesId > 0) + { + int[] valuesArray = mContext.getResources().getIntArray(valuesId); + for (int index = 0; index < valuesArray.length; index++) + { + int current = valuesArray[index]; + if (current == value) + { + return index; + } + } + } + else + { + return value; + } + + return -1; + } + + public void putXfbSetting(int which) + { + BooleanSetting xfbEnable = null; + BooleanSetting xfbReal = null; + + switch (which) + { + case 0: + xfbEnable = new BooleanSetting(SettingsFile.KEY_XFB, SettingsFile.SECTION_GFX_SETTINGS, false); + xfbReal = new BooleanSetting(SettingsFile.KEY_XFB_REAL, SettingsFile.SECTION_GFX_SETTINGS, false); + break; + + case 1: + xfbEnable = new BooleanSetting(SettingsFile.KEY_XFB, SettingsFile.SECTION_GFX_SETTINGS, true); + xfbReal = new BooleanSetting(SettingsFile.KEY_XFB_REAL, SettingsFile.SECTION_GFX_SETTINGS, false); + break; + + case 2: + xfbEnable = new BooleanSetting(SettingsFile.KEY_XFB, SettingsFile.SECTION_GFX_SETTINGS, true); + xfbReal = new BooleanSetting(SettingsFile.KEY_XFB_REAL, SettingsFile.SECTION_GFX_SETTINGS, true); + break; + } + + mView.putSetting(xfbEnable); + mView.putSetting(xfbReal); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsFragment.java new file mode 100644 index 0000000000..89e5edb056 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsFragment.java @@ -0,0 +1,141 @@ +package org.dolphinemu.dolphinemu.ui.settings; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.dolphinemu.dolphinemu.BuildConfig; +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.settings.Setting; +import org.dolphinemu.dolphinemu.model.settings.SettingSection; +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; +import org.dolphinemu.dolphinemu.ui.DividerItemDecoration; + +import java.util.ArrayList; +import java.util.HashMap; + +public final class SettingsFragment extends Fragment implements SettingsFragmentView +{ + private SettingsFragmentPresenter mPresenter = new SettingsFragmentPresenter(this); + private SettingsActivityView mActivity; + + private SettingsAdapter mAdapter; + + @Override + public void onAttach(Context context) + { + super.onAttach(context); + + mActivity = (SettingsActivityView) context; + mPresenter.onAttach(); + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setRetainInstance(true); + String menuTag = getArguments().getString(ARGUMENT_MENU_TAG); + + mAdapter = new SettingsAdapter(this, getActivity()); + + mPresenter.onCreate(menuTag); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_settings, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) + { + LinearLayoutManager manager = new LinearLayoutManager(getActivity()); + + RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list_settings); + + recyclerView.setAdapter(mAdapter); + recyclerView.setLayoutManager(manager); + recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null)); + + SettingsActivityView activity = (SettingsActivityView) getActivity(); + HashMap settings = activity.getSettings(); + + mPresenter.onViewCreated(settings); + } + + @Override + public void onDetach() + { + super.onDetach(); + mActivity = null; + + if (mAdapter != null) + { + mAdapter.closeDialog(); + } + } + + @Override + public void onSettingsFileLoaded(HashMap settings) + { + mPresenter.setSettings(settings); + } + + @Override + public void passSettingsToActivity(HashMap settings) + { + if (mActivity != null) + { + mActivity.setSettings(settings); + } + } + + @Override + public void showSettingsList(ArrayList settingsList) + { + mAdapter.setSettings(settingsList); + } + + @Override + public void loadSubMenu(String menuKey) + { + mActivity.showSettingsFragment(menuKey, true); + } + + @Override + public void showToastMessage(String message) + { + mActivity.showToastMessage(message); + } + + @Override + public void putSetting(Setting setting) + { + mPresenter.putSetting(setting); + } + + public static final String FRAGMENT_TAG = BuildConfig.APPLICATION_ID + ".fragment.settings"; + + public static final String ARGUMENT_MENU_TAG = FRAGMENT_TAG + ".menu_tag"; + + public static Fragment newInstance(String menuTag) + { + SettingsFragment fragment = new SettingsFragment(); + + Bundle arguments = new Bundle(); + arguments.putString(ARGUMENT_MENU_TAG, menuTag); + + fragment.setArguments(arguments); + return fragment; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsFragmentPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsFragmentPresenter.java new file mode 100644 index 0000000000..c338ec22fd --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsFragmentPresenter.java @@ -0,0 +1,257 @@ +package org.dolphinemu.dolphinemu.ui.settings; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.settings.BooleanSetting; +import org.dolphinemu.dolphinemu.model.settings.IntSetting; +import org.dolphinemu.dolphinemu.model.settings.Setting; +import org.dolphinemu.dolphinemu.model.settings.SettingSection; +import org.dolphinemu.dolphinemu.model.settings.view.CheckBoxSetting; +import org.dolphinemu.dolphinemu.model.settings.view.HeaderSetting; +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; +import org.dolphinemu.dolphinemu.model.settings.view.SingleChoiceSetting; +import org.dolphinemu.dolphinemu.model.settings.view.SliderSetting; +import org.dolphinemu.dolphinemu.model.settings.view.SubmenuSetting; +import org.dolphinemu.dolphinemu.utils.EGLHelper; +import org.dolphinemu.dolphinemu.utils.SettingsFile; + +import java.util.ArrayList; +import java.util.HashMap; + +public final class SettingsFragmentPresenter +{ + private SettingsFragmentView mView; + + private String mMenuTag; + + private HashMap mSettings; + private ArrayList mSettingsList; + + public SettingsFragmentPresenter(SettingsFragmentView view) + { + mView = view; + } + + public void onCreate(String menuTag) + { + mMenuTag = menuTag; + } + + public void onViewCreated(HashMap settings) + { + setSettings(settings); + } + + /** + * If the screen is rotated, the Activity will forget the settings map. This fragment + * won't, though; so rather than have the Activity reload from disk, have the fragment pass + * the settings map back to the Activity. + */ + public void onAttach() + { + if (mSettings != null) + { + mView.passSettingsToActivity(mSettings); + } + } + + public void putSetting(Setting setting) + { + mSettings.get(setting.getSection()).putSetting(setting.getKey(), setting); + } + + public void setSettings(HashMap settings) + { + if (mSettingsList == null) + { + mSettings = settings; + + loadSettingsList(); + } + else + { + mView.showSettingsList(mSettingsList); + } + } + + private void loadSettingsList() + { + ArrayList sl = new ArrayList<>(); + + switch (mMenuTag) + { + case SettingsFile.FILE_NAME_DOLPHIN: + addCoreSettings(sl); + break; + + case SettingsFile.FILE_NAME_GFX: + addGraphicsSettings(sl); + break; + + case SettingsFile.SECTION_GFX_ENHANCEMENTS: + addEnhanceSettings(sl); + break; + + case SettingsFile.SECTION_GFX_HACKS: + addHackSettings(sl); + break; + + default: + mView.showToastMessage("Unimplemented menu."); + return; + } + + mSettingsList = sl; + mView.showSettingsList(mSettingsList); + } + + private void addCoreSettings(ArrayList sl) + { + Setting cpuCore = null; + Setting dualCore = null; + Setting overclockEnable = null; + Setting overclock = null; + + if (mSettings != null) + { + cpuCore = mSettings.get(SettingsFile.SECTION_CORE).getSetting(SettingsFile.KEY_CPU_CORE); + dualCore = mSettings.get(SettingsFile.SECTION_CORE).getSetting(SettingsFile.KEY_DUAL_CORE); + overclockEnable = mSettings.get(SettingsFile.SECTION_CORE).getSetting(SettingsFile.KEY_OVERCLOCK_ENABLE); + overclock = mSettings.get(SettingsFile.SECTION_CORE).getSetting(SettingsFile.KEY_OVERCLOCK_PERCENT); + } + else + { + mSettings = new HashMap<>(); + mSettings.put(SettingsFile.SECTION_CORE, new SettingSection(SettingsFile.SECTION_CORE)); + + mView.passSettingsToActivity(mSettings); + } + + // TODO Set default value for cpuCore based on arch. + sl.add(new SingleChoiceSetting(SettingsFile.KEY_CPU_CORE, SettingsFile.SECTION_CORE, R.string.cpu_core, 0, R.array.string_emu_cores, R.array.int_emu_cores, 4, cpuCore)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_DUAL_CORE, SettingsFile.SECTION_CORE, R.string.dual_core, R.string.dual_core_descrip, true, dualCore)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_OVERCLOCK_ENABLE, SettingsFile.SECTION_CORE, R.string.overclock_enable, R.string.overclock_enable_description, false, overclockEnable)); + sl.add(new SliderSetting(SettingsFile.KEY_OVERCLOCK_PERCENT, SettingsFile.SECTION_CORE, R.string.overclock_title, 0, 400, "%", 100, overclock)); + + } + + private void addGraphicsSettings(ArrayList sl) + { + Setting showFps = null; + + if (mSettings != null) + { + showFps = mSettings.get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_SHOW_FPS); + } + else + { + mSettings = new HashMap<>(); + + mSettings.put(SettingsFile.SECTION_GFX_SETTINGS, new SettingSection(SettingsFile.SECTION_GFX_SETTINGS)); + mSettings.put(SettingsFile.SECTION_GFX_ENHANCEMENTS, new SettingSection(SettingsFile.SECTION_GFX_ENHANCEMENTS)); + mSettings.put(SettingsFile.SECTION_GFX_HACKS, new SettingSection(SettingsFile.SECTION_GFX_HACKS)); + + mView.passSettingsToActivity(mSettings); + } + + sl.add(new CheckBoxSetting(SettingsFile.KEY_SHOW_FPS, SettingsFile.SECTION_GFX_SETTINGS, R.string.show_fps, 0, true, showFps)); + + sl.add(new SubmenuSetting(null, null, R.string.enhancements, 0, SettingsFile.SECTION_GFX_ENHANCEMENTS)); + sl.add(new SubmenuSetting(null, null, R.string.hacks, 0, SettingsFile.SECTION_GFX_HACKS)); + } + + private void addEnhanceSettings(ArrayList sl) + { + Setting resolution = mSettings.get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_INTERNAL_RES); + Setting fsaa = mSettings.get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_FSAA); + Setting anisotropic = mSettings.get(SettingsFile.SECTION_GFX_ENHANCEMENTS).getSetting(SettingsFile.KEY_ANISOTROPY); + Setting efbScaledCopy = mSettings.get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_SCALED_EFB); + Setting perPixel = mSettings.get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_PER_PIXEL); + Setting forceFilter = mSettings.get(SettingsFile.SECTION_GFX_ENHANCEMENTS).getSetting(SettingsFile.KEY_FORCE_FILTERING); + Setting disableFog = mSettings.get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_DISABLE_FOG); + + sl.add(new SingleChoiceSetting(SettingsFile.KEY_INTERNAL_RES, SettingsFile.SECTION_GFX_SETTINGS, R.string.internal_resolution, R.string.internal_resolution_descrip, R.array.internalResolutionEntries, R.array.internalResolutionValues, 0, resolution)); + sl.add(new SingleChoiceSetting(SettingsFile.KEY_FSAA, SettingsFile.SECTION_GFX_SETTINGS, R.string.FSAA, R.string.FSAA_descrip, R.array.FSAAEntries, R.array.FSAAValues, 0, fsaa)); + sl.add(new SingleChoiceSetting(SettingsFile.KEY_ANISOTROPY, SettingsFile.SECTION_GFX_ENHANCEMENTS, R.string.anisotropic_filtering, R.string.anisotropic_filtering_descrip, R.array.anisotropicFilteringEntries, R.array.anisotropicFilteringValues, 0, anisotropic)); + + // TODO +// Setting shader = mSettings.get(SettingsFile.SECTION_GFX_ENHANCEMENTS).getSetting(SettingsFile.KEY_POST_SHADER) +// sl.add(new SingleChoiceSetting(.getKey(), , R.string., R.string._descrip, R.array., R.array.)); + + sl.add(new CheckBoxSetting(SettingsFile.KEY_SCALED_EFB, SettingsFile.SECTION_GFX_HACKS, R.string.scaled_efb_copy, R.string.scaled_efb_copy_descrip, true, efbScaledCopy)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_PER_PIXEL, SettingsFile.SECTION_GFX_SETTINGS, R.string.per_pixel_lighting, R.string.per_pixel_lighting_descrip, false, perPixel)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_FORCE_FILTERING, SettingsFile.SECTION_GFX_ENHANCEMENTS, R.string.force_texture_filtering, R.string.force_texture_filtering_descrip, false, forceFilter)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_DISABLE_FOG, SettingsFile.SECTION_GFX_SETTINGS, R.string.disable_fog, R.string.disable_fog_descrip, false, disableFog)); + + /* + Check if we support stereo + If we support desktop GL then we must support at least OpenGL 3.2 + If we only support OpenGLES then we need both OpenGLES 3.1 and AEP + */ + EGLHelper helper = new EGLHelper(EGLHelper.EGL_OPENGL_ES2_BIT); + + if ((helper.supportsOpenGL() && helper.GetVersion() >= 320) || + (helper.supportsGLES3() && helper.GetVersion() >= 310 && helper.SupportsExtension("GL_ANDROID_extension_pack_es31a"))) + { + sl.add(new SubmenuSetting(null, null, R.string.stereoscopy, 0, SettingsFile.SECTION_STEREOSCOPY)); + } + } + + private void addHackSettings(ArrayList sl) + { + int xfbValue = getXfbValue(); + + Setting skipEFB = mSettings.get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_SKIP_EFB); + Setting ignoreFormat = mSettings.get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_IGNORE_FORMAT); + Setting efbToTexture = mSettings.get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_EFB_TEXTURE); + Setting texCacheAccuracy = mSettings.get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_TEXCACHE_ACCURACY); + IntSetting xfb = new IntSetting(SettingsFile.KEY_XFB, SettingsFile.SECTION_GFX_HACKS, xfbValue); + Setting fastDepth = mSettings.get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_FAST_DEPTH); + Setting aspectRatio = mSettings.get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_ASPECT_RATIO); + + sl.add(new HeaderSetting(null, null, R.string.embedded_frame_buffer, 0)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_SKIP_EFB, SettingsFile.SECTION_GFX_HACKS, R.string.skip_efb_access, R.string.skip_efb_access_descrip, false, skipEFB)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_IGNORE_FORMAT, SettingsFile.SECTION_GFX_HACKS, R.string.ignore_format_changes, R.string.ignore_format_changes_descrip, false, ignoreFormat)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_EFB_TEXTURE, SettingsFile.SECTION_GFX_HACKS, R.string.efb_copy_method, R.string.efb_copy_method_descrip, true, efbToTexture)); + + sl.add(new HeaderSetting(null, null, R.string.texture_cache, 0)); + sl.add(new SingleChoiceSetting(SettingsFile.KEY_TEXCACHE_ACCURACY, SettingsFile.SECTION_GFX_HACKS, R.string.texture_cache_accuracy, R.string.texture_cache_accuracy_descrip, R.array.textureCacheAccuracyEntries, R.array.textureCacheAccuracyValues, 128, texCacheAccuracy)); + + sl.add(new HeaderSetting(null, null, R.string.external_frame_buffer, 0)); + sl.add(new SingleChoiceSetting(SettingsFile.KEY_XFB_METHOD, SettingsFile.SECTION_GFX_HACKS, R.string.external_frame_buffer, R.string.external_frame_buffer_descrip, R.array.externalFrameBufferEntries, R.array.externalFrameBufferValues, 0, xfb)); + + sl.add(new HeaderSetting(null, null, R.string.other, 0)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_FAST_DEPTH, SettingsFile.SECTION_GFX_HACKS, R.string.fast_depth_calculation, R.string.fast_depth_calculation_descrip, true, fastDepth)); + sl.add(new SingleChoiceSetting(SettingsFile.KEY_ASPECT_RATIO, SettingsFile.SECTION_GFX_HACKS, R.string.aspect_ratio, R.string.aspect_ratio_descrip, R.array.aspectRatioEntries, R.array.aspectRatioValues, 0, aspectRatio)); + } + + private int getXfbValue() + { + int xfbValue; + + try + { + boolean usingXFB = ((BooleanSetting) mSettings.get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_XFB)).getValue(); + boolean usingRealXFB = ((BooleanSetting) mSettings.get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_XFB_REAL)).getValue(); + + if (!usingXFB) + { + xfbValue = 0; + } + else if (!usingRealXFB) + { + xfbValue = 1; + } + else + { + xfbValue = 2; + } + } + catch (NullPointerException ex) + { + xfbValue = 0; + } + + return xfbValue; + } +} + diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsFragmentView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsFragmentView.java new file mode 100644 index 0000000000..c437863c94 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/SettingsFragmentView.java @@ -0,0 +1,68 @@ +package org.dolphinemu.dolphinemu.ui.settings; + +import android.app.Activity; + +import org.dolphinemu.dolphinemu.model.settings.Setting; +import org.dolphinemu.dolphinemu.model.settings.SettingSection; +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Abstraction for a screen showing a list of settings. Instances of + * this type of view will each display a layer of the setting hierarchy. + */ +public interface SettingsFragmentView +{ + /** + * Called by the containing Activity to notify the Fragment that an + * asynchronous load operation completed. + * + * @param settings The potentially-null result of the load operation. + */ + void onSettingsFileLoaded(HashMap settings); + + /** + * Pass a settings Hashmap to the containing activity, so that it can + * share the Hashmap with other SettingsFragments; useful so that rotations + * do not require an additional load operation. + * + * @param settings A Hashmap containing all the settings + */ + void passSettingsToActivity(HashMap settings); + + /** + * Pass an ArrayList to the View so that it can be displayed on screen. + * + * @param settingsList The result of converting the Hashmap to an ArrayList + */ + void showSettingsList(ArrayList settingsList); + + /** + * @return The Fragment's containing activity. + */ + Activity getActivity(); + + /** + * Tell the Fragment to tell the containing Activity to show a new + * Fragment containing a submenu of settings. + * + * @param menuKey Identifier for the settings group that should be shown. + */ + void loadSubMenu(String menuKey); + + /** + * Tell the Fragment to tell the containing activity to display a toast message. + * + * @param message Text to be shown in the Toast + */ + void showToastMessage(String message); + + /** + * Have the fragment add a setting to the Hashmap. + * + * @param setting The (possibly previously missing) new setting. + */ + void putSetting(Setting setting); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/CheckBoxSettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/CheckBoxSettingViewHolder.java new file mode 100644 index 0000000000..c65fde1d66 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/CheckBoxSettingViewHolder.java @@ -0,0 +1,57 @@ +package org.dolphinemu.dolphinemu.ui.settings.viewholder; + + +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.settings.view.CheckBoxSetting; +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; +import org.dolphinemu.dolphinemu.ui.settings.SettingsAdapter; + +public final class CheckBoxSettingViewHolder extends SettingViewHolder +{ + private CheckBoxSetting mItem; + + private TextView mTextSettingName; + private TextView mTextSettingDescription; + + private CheckBox mCheckbox; + + public CheckBoxSettingViewHolder(View itemView, SettingsAdapter adapter) + { + super(itemView, adapter); + } + + @Override + protected void findViews(View root) + { + mTextSettingName = (TextView) root.findViewById(R.id.text_setting_name); + mTextSettingDescription = (TextView) root.findViewById(R.id.text_setting_description); + mCheckbox = (CheckBox) root.findViewById(R.id.checkbox); + } + + @Override + public void bind(SettingsItem item) + { + mItem = (CheckBoxSetting) item; + + mTextSettingName.setText(item.getNameId()); + + if (item.getDescriptionId() > 0) + { + mTextSettingDescription.setText(item.getDescriptionId()); + } + + mCheckbox.setChecked(mItem.isChecked()); + } + + @Override + public void onClick(View clicked) + { + mCheckbox.toggle(); + + getAdapter().onBooleanClick(mItem, getAdapterPosition(), mCheckbox.isChecked()); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/HeaderViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/HeaderViewHolder.java new file mode 100644 index 0000000000..6e3b53d4b3 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/HeaderViewHolder.java @@ -0,0 +1,37 @@ +package org.dolphinemu.dolphinemu.ui.settings.viewholder; + +import android.view.View; +import android.widget.TextView; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; +import org.dolphinemu.dolphinemu.ui.settings.SettingsAdapter; + +public final class HeaderViewHolder extends SettingViewHolder +{ + private TextView mHeaderName; + + public HeaderViewHolder(View itemView, SettingsAdapter adapter) + { + super(itemView, adapter); + itemView.setOnClickListener(null); + } + + @Override + protected void findViews(View root) + { + mHeaderName = (TextView) root.findViewById(R.id.text_header_name); + } + + @Override + public void bind(SettingsItem item) + { + mHeaderName.setText(item.getNameId()); + } + + @Override + public void onClick(View clicked) + { + // no-op + } +} \ No newline at end of file diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SettingViewHolder.java new file mode 100644 index 0000000000..f57e9fc82c --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SettingViewHolder.java @@ -0,0 +1,51 @@ +package org.dolphinemu.dolphinemu.ui.settings.viewholder; + +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; +import org.dolphinemu.dolphinemu.ui.settings.SettingsAdapter; + +public abstract class SettingViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener +{ + private SettingsAdapter mAdapter; + + public SettingViewHolder(View itemView, SettingsAdapter adapter) + { + super(itemView); + + mAdapter = adapter; + + itemView.setOnClickListener(this); + + findViews(itemView); + } + + protected SettingsAdapter getAdapter() + { + return mAdapter; + } + + /** + * Gets handles to all this ViewHolder's child views using their XML-defined identifiers. + * + * @param root The newly inflated top-level view. + */ + protected abstract void findViews(View root); + + /** + * Called by the adapter to set this ViewHolder's child views to display the list item + * it must now represent. + * + * @param item The list item that should be represented by this ViewHolder. + */ + public abstract void bind(SettingsItem item); + + /** + * Called when this ViewHolder's view is clicked on. Implementations should usually pass + * this event up to the adapter. + * + * @param clicked The view that was clicked on. + */ + public abstract void onClick(View clicked); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SingleChoiceViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SingleChoiceViewHolder.java new file mode 100644 index 0000000000..add1b76637 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SingleChoiceViewHolder.java @@ -0,0 +1,48 @@ +package org.dolphinemu.dolphinemu.ui.settings.viewholder; + +import android.view.View; +import android.widget.TextView; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; +import org.dolphinemu.dolphinemu.model.settings.view.SingleChoiceSetting; +import org.dolphinemu.dolphinemu.ui.settings.SettingsAdapter; + +public final class SingleChoiceViewHolder extends SettingViewHolder +{ + private SingleChoiceSetting mItem; + + private TextView mTextSettingName; + private TextView mTextSettingDescription; + + public SingleChoiceViewHolder(View itemView, SettingsAdapter adapter) + { + super(itemView, adapter); + } + + @Override + protected void findViews(View root) + { + mTextSettingName = (TextView) root.findViewById(R.id.text_setting_name); + mTextSettingDescription = (TextView) root.findViewById(R.id.text_setting_description); + } + + @Override + public void bind(SettingsItem item) + { + mItem = (SingleChoiceSetting) item; + + mTextSettingName.setText(item.getNameId()); + + if (item.getDescriptionId() > 0) + { + mTextSettingDescription.setText(item.getDescriptionId()); + } + } + + @Override + public void onClick(View clicked) + { + getAdapter().onSingleChoiceClick(mItem); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SliderViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SliderViewHolder.java new file mode 100644 index 0000000000..9615c720fb --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SliderViewHolder.java @@ -0,0 +1,50 @@ +package org.dolphinemu.dolphinemu.ui.settings.viewholder; + + +import android.view.View; +import android.widget.TextView; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; +import org.dolphinemu.dolphinemu.model.settings.view.SliderSetting; +import org.dolphinemu.dolphinemu.ui.settings.SettingsAdapter; + +public final class SliderViewHolder extends SettingViewHolder +{ + private SliderSetting mItem; + + private TextView mTextSettingName; + private TextView mTextSettingDescription; + + public SliderViewHolder(View itemView, SettingsAdapter adapter) + { + super(itemView, adapter); + } + + @Override + protected void findViews(View root) + { + mTextSettingName = (TextView) root.findViewById(R.id.text_setting_name); + mTextSettingDescription = (TextView) root.findViewById(R.id.text_setting_description); + } + + @Override + public void bind(SettingsItem item) + { + mItem = (SliderSetting) item; + + mTextSettingName.setText(item.getNameId()); + + if (item.getDescriptionId() > 0) + { + mTextSettingDescription.setText(item.getDescriptionId()); + } + } + + @Override + public void onClick(View clicked) + { + getAdapter().onSliderClick(mItem); + } +} + diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SubmenuViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SubmenuViewHolder.java new file mode 100644 index 0000000000..4a8243cec7 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/settings/viewholder/SubmenuViewHolder.java @@ -0,0 +1,48 @@ +package org.dolphinemu.dolphinemu.ui.settings.viewholder; + +import android.view.View; +import android.widget.TextView; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.settings.view.SettingsItem; +import org.dolphinemu.dolphinemu.model.settings.view.SubmenuSetting; +import org.dolphinemu.dolphinemu.ui.settings.SettingsAdapter; + +public final class SubmenuViewHolder extends SettingViewHolder +{ + private SubmenuSetting mItem; + + private TextView mTextSettingName; + private TextView mTextSettingDescription; + + public SubmenuViewHolder(View itemView, SettingsAdapter adapter) + { + super(itemView, adapter); + } + + @Override + protected void findViews(View root) + { + mTextSettingName = (TextView) root.findViewById(R.id.text_setting_name); + mTextSettingDescription = (TextView) root.findViewById(R.id.text_setting_description); + } + + @Override + public void bind(SettingsItem item) + { + mItem = (SubmenuSetting) item; + + mTextSettingName.setText(item.getNameId()); + + if (item.getDescriptionId() > 0) + { + mTextSettingDescription.setText(item.getDescriptionId()); + } + } + + @Override + public void onClick(View clicked) + { + getAdapter().onSubmenuClick(mItem); + } +} \ No newline at end of file diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SettingsFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SettingsFile.java new file mode 100644 index 0000000000..865e3371ae --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SettingsFile.java @@ -0,0 +1,302 @@ +package org.dolphinemu.dolphinemu.utils; + +import android.os.Environment; +import android.support.annotation.NonNull; + +import org.dolphinemu.dolphinemu.model.settings.BooleanSetting; +import org.dolphinemu.dolphinemu.model.settings.FloatSetting; +import org.dolphinemu.dolphinemu.model.settings.IntSetting; +import org.dolphinemu.dolphinemu.model.settings.Setting; +import org.dolphinemu.dolphinemu.model.settings.SettingSection; +import org.dolphinemu.dolphinemu.model.settings.StringSetting; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Set; + +import rx.Observable; +import rx.Subscriber; + +/** + * Contains static methods for interacting with .ini files in which settings are stored. + */ +public final class SettingsFile +{ + public static final String FILE_NAME_DOLPHIN = "Dolphin"; + public static final String FILE_NAME_GFX = "GFX"; + public static final String FILE_NAME_GCPAD = "GCPadNew"; + public static final String FILE_NAME_WIIMOTE = "WiimoteNew"; + + public static final String SECTION_CORE = "Core"; + + public static final String SECTION_GFX_SETTINGS = "Settings"; + public static final String SECTION_GFX_ENHANCEMENTS = "Enhancements"; + public static final String SECTION_GFX_HACKS = "Hacks"; + + public static final String SECTION_STEREOSCOPY = "Stereoscopy"; + + public static final String KEY_CPU_CORE = "CPUCore"; + public static final String KEY_DUAL_CORE = "CPUThread"; + public static final String KEY_OVERCLOCK_ENABLE = "OverclockEnable"; + public static final String KEY_OVERCLOCK_PERCENT = "Overclock"; + public static final String KEY_VIDEO_BACKEND = "GFXBackend"; + + public static final String KEY_SHOW_FPS = "ShowFPS"; + public static final String KEY_INTERNAL_RES = "EFBScale"; + public static final String KEY_FSAA = "MSAA"; + public static final String KEY_ANISOTROPY = "MaxAnisotropy"; + public static final String KEY_POST_SHADER = "PostProcessingShader"; + public static final String KEY_SCALED_EFB = "EFBScaledCopy"; + public static final String KEY_PER_PIXEL = "EnablePixelLighting"; + public static final String KEY_FORCE_FILTERING = "ForceFiltering"; + public static final String KEY_DISABLE_FOG = "DisableFog"; + + public static final String KEY_STEREO_MODE = "StereoMode"; + public static final String KEY_STEREO_DEPTH = "StereoDepth"; + public static final String KEY_STEREO_CONV = "StereoConvergencePercentage"; + public static final String KEY_STEREO_SWAP = "StereoSwapEyes"; + + public static final String KEY_SKIP_EFB = "EFBAccessEnable"; + public static final String KEY_IGNORE_FORMAT = "EFBEmulateFormatChanges"; + public static final String KEY_EFB_COPY = "EFBCopyEnable"; + public static final String KEY_EFB_TEXTURE = "EFBToTextureEnable"; + public static final String KEY_EFB_CACHE = "EFBCopyCacheEnable"; + public static final String KEY_TEXCACHE_ACCURACY = "SafeTextureCacheColorSamples"; + public static final String KEY_XFB = "UseXFB"; + public static final String KEY_XFB_REAL = "UseRealXFB"; + public static final String KEY_FAST_DEPTH= "FastDepthCalc"; + public static final String KEY_ASPECT_RATIO= "AspectRatio"; + + // Internal only, not actually found in settings file. + public static final String KEY_XFB_METHOD = "XFBMethod"; + + private SettingsFile() + { + } + + /** + * Reads a given .ini file from disk and returns it as an Rx Observable. If successful, + * this Observable emits a Hashmap of SettingSections, themselves effectively a Hashmap of + * key/value settings. If unsuccessful, the observer notifies the subscriber of why it failed. + * + * @param fileName The name of the settings file without a path or extension. + * @return An Observable that emits a Hashmap of the file's contents, then completes. + */ + public static Observable> readFile(final String fileName) + { + return Observable.create(new Observable.OnSubscribe>() + { + @Override + public void call(Subscriber> subscriber) + { + + HashMap sections = new HashMap<>(); + + File ini = getSettingsFile(fileName); + + BufferedReader reader = null; + + try + { + reader = new BufferedReader(new FileReader(ini)); + + SettingSection current = null; + for (String line; (line = reader.readLine()) != null; ) + { + if (line.startsWith("[") && line.endsWith("]")) + { + current = sectionFromLine(line); + sections.put(current.getName(), current); + } + else if ((current != null) && line.contains("=")) + { + Setting setting = settingFromLine(current, line); + current.putSetting(setting.getKey(), setting); + } + } + } + catch (FileNotFoundException e) + { + Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.getMessage()); + subscriber.onError(e); + } + catch (IOException e) + { + Log.error("[SettingsFile] Error reading from: " + fileName + ".ini: " + e.getMessage()); + subscriber.onError(e); + } finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (IOException e) + { + Log.error("[SettingsFile] Error closing: " + fileName + ".ini: " + e.getMessage()); + subscriber.onError(e); + } + } + } + + subscriber.onNext(sections); + subscriber.onCompleted(); + } + }); + } + + /** + * Saves a Settings Hashmap to a given .ini file on disk, returning the operation + * as an Rx Observable. If successful, this Observable emits an event to notify subscribers; + * if unsuccessful, the observer notifies the subscriber of why it failed. + * + * @param fileName The target filename without a path or extension. + * @param sections The hashmap containing the Settings we want to serialize. + * @return An Observable representing the operation. + */ + public static Observable saveFile(final String fileName, final HashMap sections) + { + return Observable.create(new Observable.OnSubscribe() + { + @Override + public void call(Subscriber subscriber) + { + File ini = getSettingsFile(fileName); + + PrintWriter writer = null; + try + { + writer = new PrintWriter(ini, "UTF-8"); + + Set keySet = sections.keySet(); + + for (String key : keySet) + { + SettingSection section = sections.get(key); + writeSection(writer, section); + } + } + catch (FileNotFoundException e) + { + Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.getMessage()); + subscriber.onError(e); + } + catch (UnsupportedEncodingException e) + { + Log.error("[SettingsFile] Bad encoding; please file a bug report: " + fileName + ".ini: " + e.getMessage()); + subscriber.onError(e); + } finally + { + if (writer != null) + { + writer.close(); + } + } + + subscriber.onNext(true); + subscriber.onCompleted(); + } + }); + } + + @NonNull + private static File getSettingsFile(String fileName) + { + String storagePath = Environment.getExternalStorageDirectory().getAbsolutePath(); + return new File(storagePath + "/dolphin-emu/Config/" + fileName + ".ini"); + } + + private static SettingSection sectionFromLine(String line) + { + String sectionName = line.substring(1, line.length() - 1); + return new SettingSection(sectionName); + } + + /** + * For a line of text, determines what type of data is being represented, and returns + * a Setting object containing this data. + * + * @param current The section currently being parsed by the consuming method. + * @param line The line of text being parsed. + * @return A typed Setting containing the key/value contained in the line. + */ + private static Setting settingFromLine(SettingSection current, String line) + { + String[] splitLine = line.split("="); + + String key = splitLine[0].trim(); + String value = splitLine[1].trim(); + + try + { + int valueAsInt = Integer.valueOf(value); + + return new IntSetting(key, current.getName(), valueAsInt); + } + catch (NumberFormatException ex) + { + } + + try + { + float valueAsFloat = Float.valueOf(value); + + return new FloatSetting(key, current.getName(), valueAsFloat); + } + catch (NumberFormatException ex) + { + } + + switch (value) + { + case "True": + return new BooleanSetting(key, current.getName(), true); + case "False": + return new BooleanSetting(key, current.getName(), false); + default: + return new StringSetting(key, current.getName(), value); + } + } + + /** + * Writes the contents of a Section hashmap to disk. + * + * @param writer A PrintWriter pointed at a file on disk. + * @param section A section containing settings to be written to the file. + */ + private static void writeSection(PrintWriter writer, SettingSection section) + { + // Write the section header. + String header = sectionAsString(section); + writer.println(header); + + // Write this section's values. + HashMap settings = section.getSettings(); + Set keySet = settings.keySet(); + + for (String key : keySet) + { + Setting setting = settings.get(key); + String settingString = settingAsString(setting); + + writer.println(settingString); + } + } + + private static String sectionAsString(SettingSection section) + { + return "[" + section.getName() + "]"; + } + + private static String settingAsString(Setting setting) + { + return setting.getKey() + " = " + setting.getValueAsString(); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SliderPreference.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SliderPreference.java deleted file mode 100644 index da896cf590..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SliderPreference.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.dolphinemu.dolphinemu.utils; - -import android.app.AlertDialog; -import android.content.Context; -import android.os.Bundle; -import android.preference.DialogPreference; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.SeekBar; -import android.widget.TextView; -import android.widget.Toast; - -import org.dolphinemu.dolphinemu.R; - -public class SliderPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener, View.OnClickListener -{ - private static final String androidns = "http://schemas.android.com/apk/res/android"; - - // SeekBar - private int m_max, m_value; - private String m_key; - private SeekBar m_seekbar; - - // TextView - private TextView m_textview; - - public SliderPreference(Context context, AttributeSet attrs) - { - super(context, attrs); - - // Seekbar values - m_value = attrs.getAttributeIntValue(androidns, "defaultValue", 0); - m_max = attrs.getAttributeIntValue(androidns, "max", 100); - m_key = attrs.getAttributeValue(androidns, "key"); - } - - @Override - protected View onCreateDialogView() - { - LayoutInflater inflater = LayoutInflater.from(getContext()); - LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.slider_layout, null, false); - - m_seekbar = (SeekBar) layout.findViewById(R.id.sliderSeekBar); - m_textview = (TextView) layout.findViewById(R.id.sliderTextView); - - if (shouldPersist()) - { - if (m_key != null && m_key.equals("Overclock")) - { - Toast.makeText(getContext(), getContext().getString(R.string.overclock_warning), - Toast.LENGTH_LONG).show(); - - float valueAsFloat = Float.valueOf(getPersistedString(Integer.toString(m_value))); - float valueAsPercent = valueAsFloat * 100; - - m_value = Math.round(valueAsPercent); - } - else - { - m_value = Integer.valueOf(getPersistedString(Integer.toString(m_value))); - } - } - - m_seekbar.setMax(m_max); - m_seekbar.setProgress(m_value); - setProgressText(m_value); - m_seekbar.setOnSeekBarChangeListener(this); - - return layout; - } - - // SeekBar overrides - @Override - public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) - { - m_value = value; - setProgressText(value); - } - - @Override - public void onStartTrackingTouch(SeekBar seek) - { - } - - @Override - public void onStopTrackingTouch(SeekBar seek) - { - } - - void setProgressText(int value) - { - m_textview.setText(String.valueOf(value)); - } - - @Override - public void showDialog(Bundle state) - { - super.showDialog(state); - - Button positiveButton = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE); - positiveButton.setOnClickListener(this); - } - - @Override - public void onClick(View v) - { - if (shouldPersist()) - { - String valueToSave; - if (m_key != null && m_key.equals("Overclock")) - { - float valueAsFloat = m_value / 100.0f; - valueToSave = Float.toString(valueAsFloat); - } - else - { - valueToSave = Integer.toString(m_seekbar.getProgress()); - } - - persistString(valueToSave); - callChangeListener(m_seekbar.getProgress()); - } - ((AlertDialog) getDialog()).dismiss(); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/UserPreferences.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/UserPreferences.java deleted file mode 100644 index a5d8883b95..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/UserPreferences.java +++ /dev/null @@ -1,495 +0,0 @@ -/** - * Copyright 2013 Dolphin Emulator Project - * Licensed under GPLv2+ - * Refer to the license.txt file included. - */ - -package org.dolphinemu.dolphinemu.utils; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Build; -import android.preference.PreferenceManager; - -import org.dolphinemu.dolphinemu.NativeLibrary; - -/** - * A class that retrieves all of the set user preferences in Android, in a safe way. - *

- * If any preferences are added to this emulator, an accessor for that preference - * should be added here. This way lengthy calls to getters from SharedPreferences - * aren't made necessary. - */ -public final class UserPreferences -{ - private UserPreferences() - { - // Disallows instantiation. - } - - /** - * Loads the settings stored in the Dolphin ini config files to the shared preferences of this front-end. - * - * @param ctx The context used to retrieve the SharedPreferences instance. - */ - public static void LoadIniToPrefs(Context ctx) - { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); - - // Get an editor. - SharedPreferences.Editor editor = prefs.edit(); - - // Add the settings. - if (Build.CPU_ABI.contains("arm64")) - editor.putString("cpuCorePref", getConfig("Dolphin.ini", "Core", "CPUCore", "4")); - else - editor.putString("cpuCorePref", getConfig("Dolphin.ini", "Core", "CPUCore", "3")); - - editor.putBoolean("dualCorePref", getConfig("Dolphin.ini", "Core", "CPUThread", "True").equals("True")); - editor.putBoolean("OverclockEnable", getConfig("Dolphin.ini", "Core", "OverclockEnable", "False").equals("True")); - editor.putString("Overclock", getConfig("Dolphin.ini", "Core", "Overclock", "100")); - - // Load analog ranges from GCPadNew.ini and WiimoteNew.ini - editor.putString("mainRadius0", getConfig("GCPadNew.ini", "GCPad1", "Main Stick/Radius", "100,000000")); - editor.putString("cStickRadius0", getConfig("GCPadNew.ini", "GCPad1", "C-Stick/Radius", "100,000000")); - editor.putString("inputThres0", getConfig("GCPadNew.ini", "GCPad1", "Triggers/Threshold", "90,000000")); - editor.putString("mainRadius1", getConfig("GCPadNew.ini", "GCPad2", "Main Stick/Radius", "100,000000")); - editor.putString("cStickRadius1", getConfig("GCPadNew.ini", "GCPad2", "C-Stick/Radius", "100,000000")); - editor.putString("inputThres1", getConfig("GCPadNew.ini", "GCPad2", "Triggers/Threshold", "90,000000")); - editor.putString("mainRadius2", getConfig("GCPadNew.ini", "GCPad3", "Main Stick/Radius", "100,000000")); - editor.putString("cStickRadius2", getConfig("GCPadNew.ini", "GCPad3", "C-Stick/Radius", "100,000000")); - editor.putString("inputThres2", getConfig("GCPadNew.ini", "GCPad3", "Triggers/Threshold", "90,000000")); - editor.putString("mainRadius3", getConfig("GCPadNew.ini", "GCPad4", "Main Stick/Radius", "100,000000")); - editor.putString("cStickRadius3", getConfig("GCPadNew.ini", "GCPad4", "C-Stick/Radius", "100,000000")); - editor.putString("inputThres3", getConfig("GCPadNew.ini", "GCPad4", "Triggers/Threshold", "90,000000")); - - editor.putString("tiltRange4", getConfig("WiimoteNew.ini", "Wiimote1", "Tilt/Modifier/Range", "50,00000")); - editor.putString("tiltRange5", getConfig("WiimoteNew.ini", "Wiimote2", "Tilt/Modifier/Range", "50,00000")); - editor.putString("tiltRange6", getConfig("WiimoteNew.ini", "Wiimote3", "Tilt/Modifier/Range", "50,00000")); - editor.putString("tiltRange7", getConfig("WiimoteNew.ini", "Wiimote4", "Tilt/Modifier/Range", "50,00000")); - - editor.putString("nunchukRadius4", getConfig("WiimoteNew.ini", "Wiimote1", "Nunchuk/Stick/Radius", "100,000000")); - editor.putString("nunchukRange4", getConfig("WiimoteNew.ini", "Wiimote1", "Nunchuk/Tilt/Modifier/Range", "50,00000")); - editor.putString("nunchukRadius5", getConfig("WiimoteNew.ini", "Wiimote2", "Nunchuk/Stick/Radius", "100,000000")); - editor.putString("nunchukRange5", getConfig("WiimoteNew.ini", "Wiimote2", "Nunchuk/Tilt/Modifier/Range", "50,00000")); - editor.putString("nunchukRadius6", getConfig("WiimoteNew.ini", "Wiimote3", "Nunchuk/Stick/Radius", "100,000000")); - editor.putString("nunchukRange6", getConfig("WiimoteNew.ini", "Wiimote3", "Nunchuk/Tilt/Modifier/Range", "50,00000")); - editor.putString("nunchukRadius7", getConfig("WiimoteNew.ini", "Wiimote4", "Nunchuk/Stick/Radius", "100,000000")); - editor.putString("nunchukRange7", getConfig("WiimoteNew.ini", "Wiimote4", "Nunchuk/Tilt/Modifier/Range", "50,00000")); - - editor.putString("classicLRadius4", getConfig("WiimoteNew.ini", "Wiimote1", "Classic/Left Stick/Radius", "100,000000")); - editor.putString("classicRRadius4", getConfig("WiimoteNew.ini", "Wiimote1", "Classic/Right Stick/Radius", "100,000000")); - editor.putString("classicThres4", getConfig("WiimoteNew.ini", "Wiimote1", "Classic/Triggers/Threshold", "90,000000")); - editor.putString("classicLRadius5", getConfig("WiimoteNew.ini", "Wiimote2", "Classic/Left Stick/Radius", "100,000000")); - editor.putString("classicRRadius5", getConfig("WiimoteNew.ini", "Wiimote2", "Classic/Right Stick/Radius", "100,000000")); - editor.putString("classicThres5", getConfig("WiimoteNew.ini", "Wiimote2", "Classic/Triggers/Threshold", "90,000000")); - editor.putString("classicLRadius6", getConfig("WiimoteNew.ini", "Wiimote3", "Classic/Left Stick/Radius", "100,000000")); - editor.putString("classicRRadius6", getConfig("WiimoteNew.ini", "Wiimote3", "Classic/Right Stick/Radius", "100,000000")); - editor.putString("classicThres6", getConfig("WiimoteNew.ini", "Wiimote3", "Classic/Triggers/Threshold", "90,000000")); - editor.putString("classicLRadius7", getConfig("WiimoteNew.ini", "Wiimote4", "Classic/Left Stick/Radius", "100,000000")); - editor.putString("classicRRadius7", getConfig("WiimoteNew.ini", "Wiimote4", "Classic/Right Stick/Radius", "100,000000")); - editor.putString("classicThres7", getConfig("WiimoteNew.ini", "Wiimote4", "Classic/Triggers/Threshold", "90,000000")); - - editor.putString("guitarRadius4", getConfig("WiimoteNew.ini", "Wiimote1", "Guitar/Stick/Radius", "100,000000")); - editor.putString("guitarRadius5", getConfig("WiimoteNew.ini", "Wiimote2", "Guitar/Stick/Radius", "100,000000")); - editor.putString("guitarRadius6", getConfig("WiimoteNew.ini", "Wiimote3", "Guitar/Stick/Radius", "100,000000")); - editor.putString("guitarRadius7", getConfig("WiimoteNew.ini", "Wiimote4", "Guitar/Stick/Radius", "100,000000")); - - editor.putString("drumsRadius4", getConfig("WiimoteNew.ini", "Wiimote1", "Drums/Stick/Radius", "100,000000")); - editor.putString("drumsRadius5", getConfig("WiimoteNew.ini", "Wiimote2", "Drums/Stick/Radius", "100,000000")); - editor.putString("drumsRadius6", getConfig("WiimoteNew.ini", "Wiimote3", "Drums/Stick/Radius", "100,000000")); - editor.putString("drumsRadius7", getConfig("WiimoteNew.ini", "Wiimote4", "Drums/Stick/Radius", "100,000000")); - - editor.putString("turntableRadius4", getConfig("WiimoteNew.ini", "Wiimote1", "Turntable/Stick/Radius", "100,000000")); - editor.putString("turntableRadius5", getConfig("WiimoteNew.ini", "Wiimote2", "Turntable/Stick/Radius", "100,000000")); - editor.putString("turntableRadius6", getConfig("WiimoteNew.ini", "Wiimote3", "Turntable/Stick/Radius", "100,000000")); - editor.putString("turntableRadius7", getConfig("WiimoteNew.ini", "Wiimote4", "Turntable/Stick/Radius", "100,000000")); - - // Load Wiimote Extension settings from WiimoteNew.ini - editor.putString("wiimoteExtension4", getConfig("WiimoteNew.ini", "Wiimote1", "Extension", "None")); - editor.putString("wiimoteExtension5", getConfig("WiimoteNew.ini", "Wiimote2", "Extension", "None")); - editor.putString("wiimoteExtension6", getConfig("WiimoteNew.ini", "Wiimote3", "Extension", "None")); - editor.putString("wiimoteExtension7", getConfig("WiimoteNew.ini", "Wiimote4", "Extension", "None")); - - editor.putString("gpuPref", getConfig("Dolphin.ini", "Core", "GFXBackend", "OGL")); - editor.putBoolean("showFPS", getConfig("GFX.ini", "Settings", "ShowFPS", "False").equals("True")); - editor.putBoolean("drawOnscreenControls", getConfig("Dolphin.ini", "Android", "ScreenControls", "True").equals("True")); - - editor.putString("internalResolution", getConfig("GFX.ini", "Settings", "EFBScale", "2")); - editor.putString("FSAA", getConfig("GFX.ini", "Settings", "MSAA", "0")); - editor.putString("anisotropicFiltering", getConfig("GFX.ini", "Enhancements", "MaxAnisotropy", "0")); - editor.putString("postProcessingShader", getConfig("GFX.ini", "Enhancements", "PostProcessingShader", "")); - editor.putBoolean("scaledEFBCopy", getConfig("GFX.ini", "Hacks", "EFBScaledCopy", "True").equals("True")); - editor.putBoolean("perPixelLighting", getConfig("GFX.ini", "Settings", "EnablePixelLighting", "False").equals("True")); - editor.putBoolean("forceTextureFiltering", getConfig("GFX.ini", "Enhancements", "ForceFiltering", "False").equals("True")); - editor.putBoolean("disableFog", getConfig("GFX.ini", "Settings", "DisableFog", "False").equals("True")); - editor.putBoolean("skipEFBAccess", getConfig("GFX.ini", "Hacks", "EFBAccessEnable", "False").equals("True")); - editor.putBoolean("ignoreFormatChanges", getConfig("GFX.ini", "Hacks", "EFBEmulateFormatChanges", "False").equals("True")); - editor.putString("stereoscopyMode", getConfig("GFX.ini", "Stereoscopy", "StereoMode", "0")); - editor.putBoolean("stereoSwapEyes", getConfig("GFX.ini", "Stereoscopy", "StereoSwapEyes", "False").equals("True")); - editor.putString("stereoDepth", getConfig("GFX.ini", "Stereoscopy", "StereoDepth", "20")); - editor.putString("stereoConvergencePercentage", getConfig("GFX.ini", "Stereoscopy", "StereoConvergencePercentage", "100")); - editor.putBoolean("enableController1", getConfig("Dolphin.ini", "Core", "SIDevice0", "6") == "6"); - editor.putBoolean("enableController2", getConfig("Dolphin.ini", "Core", "SIDevice1", "0") == "6"); - editor.putBoolean("enableController3", getConfig("Dolphin.ini", "Core", "SIDevice2", "0") == "6"); - editor.putBoolean("enableController4", getConfig("Dolphin.ini", "Core", "SIDevice3", "0") == "6"); - - String efbCopyOn = getConfig("GFX.ini", "Hacks", "EFBCopyEnable", "True"); - String efbToTexture = getConfig("GFX.ini", "Hacks", "EFBToTextureEnable", "True"); - String efbCopyCache = getConfig("GFX.ini", "Hacks", "EFBCopyCacheEnable", "False"); - - if (efbCopyOn.equals("False")) - { - editor.putString("efbCopyMethod", "Off"); - } - else if (efbCopyOn.equals("True") && efbToTexture.equals("True")) - { - editor.putString("efbCopyMethod", "Texture"); - } - else if(efbCopyOn.equals("True") && efbToTexture.equals("False") && efbCopyCache.equals("False")) - { - editor.putString("efbCopyMethod", "RAM (uncached)"); - } - else if(efbCopyOn.equals("True") && efbToTexture.equals("False") && efbCopyCache.equals("True")) - { - editor.putString("efbCopyMethod", "RAM (cached)"); - } - - editor.putString("textureCacheAccuracy", getConfig("GFX.ini", "Settings", "SafeTextureCacheColorSamples", "128")); - - String usingXFB = getConfig("GFX.ini", "Settings", "UseXFB", "False"); - String usingRealXFB = getConfig("GFX.ini", "Settings", "UseRealXFB", "False"); - - if (usingXFB.equals("False")) - { - editor.putString("externalFrameBuffer", "Disabled"); - } - else if (usingXFB.equals("True") && usingRealXFB.equals("False")) - { - editor.putString("externalFrameBuffer", "Virtual"); - } - else if (usingXFB.equals("True") && usingRealXFB.equals("True")) - { - editor.putString("externalFrameBuffer", "Real"); - } - - editor.putBoolean("fastDepthCalculation", getConfig("GFX.ini", "Settings", "FastDepthCalc", "True").equals("True")); - editor.putString("aspectRatio", getConfig("GFX.ini", "Settings", "AspectRatio", "0")); - - // Apply the changes. - editor.apply(); - } - - // Small utility method that shortens calls to NativeLibrary.GetConfig. - private static String getConfig(String ini, String section, String key, String defaultValue) - { - return NativeLibrary.GetConfig(ini, section, key, defaultValue); - } - - /** - * Writes the preferences set in the front-end to the Dolphin ini files. - * - * @param ctx The context used to retrieve the user settings. - * */ - public static void SavePrefsToIni(Context ctx) - { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); - - // Whether or not the user is using dual core. - boolean isUsingDualCore = prefs.getBoolean("dualCorePref", true); - - // Current CPU core being used. Falls back to interpreter upon error. - String currentEmuCore = prefs.getString("cpuCorePref", "0"); - - boolean overclockEnabled = prefs.getBoolean("OverclockEnable", false); - String overclockSetting = prefs.getString("Overclock", "100"); - - // Current GC analog range setup. Falls back to default upon error. - String currentMainRadius0 = prefs.getString("mainRadius0", "100,000000"); - String currentCStickRadius0 = prefs.getString("cStickRadius0", "100,000000"); - String currentInputThres0 = prefs.getString("inputThres0", "90,000000"); - String currentMainRadius1 = prefs.getString("mainRadius1", "100,000000"); - String currentCStickRadius1 = prefs.getString("cStickRadius1", "100,000000"); - String currentInputThres1 = prefs.getString("inputThres1", "90,000000"); - String currentMainRadius2 = prefs.getString("mainRadius2", "100,000000"); - String currentCStickRadius2 = prefs.getString("cStickRadius2", "100,000000"); - String currentInputThres2 = prefs.getString("inputThres2", "90,000000"); - String currentMainRadius3 = prefs.getString("mainRadius3", "100,000000"); - String currentCStickRadius3 = prefs.getString("cStickRadius3", "100,000000"); - String currentInputThres3 = prefs.getString("inputThres3", "90,000000"); - - // Current Wii analog range setup. Falls back to default on error. - String currentTiltRange4 = prefs.getString("tiltRange4", "50,000000"); - String currentTiltRange5 = prefs.getString("tiltRange5", "50,000000"); - String currentTiltRange6 = prefs.getString("tiltRange6", "50,000000"); - String currentTiltRange7 = prefs.getString("tiltRange7", "50,000000"); - - // Current Nunchuk analog range setup. Falls back to default upon error. - String currentNunchukRadius4 = prefs.getString("nunchukRadius4", "100,000000"); - String currentNunchukRange4 = prefs.getString("nunchukRange4", "50,000000"); - String currentNunchukRadius5 = prefs.getString("nunchukRadius5", "100,000000"); - String currentNunchukRange5 = prefs.getString("nunchukRange5", "50,000000"); - String currentNunchukRadius6 = prefs.getString("nunchukRadius6", "100,000000"); - String currentNunchukRange6 = prefs.getString("nunchukRange6", "50,000000"); - String currentNunchukRadius7 = prefs.getString("nunchukRadius7", "100,000000"); - String currentNunchukRange7 = prefs.getString("nunchukRange7", "50,000000"); - - // Current Classic analog range setup. Falls back to 100,000000 upon error. - String currentClassicLRadius4 = prefs.getString("classicLRadius4", "100,000000"); - String currentClassicRRadius4 = prefs.getString("classicRRadius4", "100,000000"); - String currentClassicThres4 = prefs.getString("classicThres4", "90,000000"); - String currentClassicLRadius5 = prefs.getString("classicLRadius5", "100,000000"); - String currentClassicRRadius5 = prefs.getString("classicRRadius5", "100,000000"); - String currentClassicThres5 = prefs.getString("classicThres5", "90,000000"); - String currentClassicLRadius6 = prefs.getString("classicLRadius6", "100,000000"); - String currentClassicRRadius6 = prefs.getString("classicRRadius6", "100,000000"); - String currentClassicThres6 = prefs.getString("classicThres6", "90,000000"); - String currentClassicLRadius7 = prefs.getString("classicLRadius7", "100,000000"); - String currentClassicRRadius7 = prefs.getString("classicRRadius7", "100,000000"); - String currentClassicThres7 = prefs.getString("classicThres7", "90,000000"); - - // Current Guitar analog range setup. Falls back to default upon error. - String currentGuitarRadius4 = prefs.getString("guitarRadius4", "100,000000"); - String currentGuitarRadius5 = prefs.getString("guitarRadius5", "100,000000"); - String currentGuitarRadius6 = prefs.getString("guitarRadius6", "100,000000"); - String currentGuitarRadius7 = prefs.getString("guitarRadius7", "100,000000"); - - // Current Drums modifier Radius setup. Falls back to default upon error. - String currentDrumsRadius4 = prefs.getString("drumsRadius4", "100,000000"); - String currentDrumsRadius5 = prefs.getString("drumsRadius5", "100,000000"); - String currentDrumsRadius6 = prefs.getString("drumsRadius6", "100,000000"); - String currentDrumsRadius7 = prefs.getString("drumsRadius7", "100,000000"); - - // Current Turntable analog range setup. Falls back to default upon error. - String currentTurntableRadius4 = prefs.getString("turntableRadius4", "100,000000"); - String currentTurntableRadius5 = prefs.getString("turntableRadius5", "100,000000"); - String currentTurntableRadius6 = prefs.getString("turntableRadius6", "100,000000"); - String currentTurntableRadius7 = prefs.getString("turntableRadius7", "100,000000"); - - // Current wiimote extension setup. Falls back to no extension upon error. - String currentWiimoteExtension4 = prefs.getString("wiimoteExtension4", "None"); - String currentWiimoteExtension5 = prefs.getString("wiimoteExtension5", "None"); - String currentWiimoteExtension6 = prefs.getString("wiimoteExtension6", "None"); - String currentWiimoteExtension7 = prefs.getString("wiimoteExtension7", "None"); - - // Current video backend being used. Falls back to software rendering upon error. - String currentVideoBackend = prefs.getString("gpuPref", "Software Rendering"); - - // Whether or not FPS will be displayed on-screen. - boolean showingFPS = prefs.getBoolean("showFPS", false); - - // Whether or not to draw on-screen controls. - boolean drawingOnscreenControls = prefs.getBoolean("drawOnscreenControls", true); - - // Whether or not to ignore all EFB access requests from the CPU. - boolean skipEFBAccess = prefs.getBoolean("skipEFBAccess", false); - - // Whether or not to ignore changes to the EFB format. - boolean ignoreFormatChanges = prefs.getBoolean("ignoreFormatChanges", false); - - // EFB copy method to use. - String efbCopyMethod = prefs.getString("efbCopyMethod", "Texture"); - - // Texture cache accuracy. Falls back to "Fast" up error. - String textureCacheAccuracy = prefs.getString("textureCacheAccuracy", "128"); - - // External frame buffer emulation. Falls back to disabled upon error. - String externalFrameBuffer = prefs.getString("externalFrameBuffer", "Disabled"); - - // Whether or not to use fast depth calculation. - boolean useFastDepthCalc = prefs.getBoolean("fastDepthCalculation", true); - - // Aspect ratio selection - String aspectRatio = prefs.getString("aspectRatio", "0"); - - // Internal resolution. Falls back to 1x Native upon error. - String internalResolution = prefs.getString("internalResolution", "2"); - - // FSAA Level. Falls back to 1x upon error. - String FSAALevel = prefs.getString("FSAA", "0"); - - // Anisotropic Filtering Level. Falls back to 1x upon error. - String anisotropicFiltLevel = prefs.getString("anisotropicFiltering", "0"); - - // Post processing shader setting - String postProcessing = prefs.getString("postProcessingShader", ""); - - // Whether or not Scaled EFB copies are used. - boolean usingScaledEFBCopy = prefs.getBoolean("scaledEFBCopy", true); - - // Whether or not per-pixel lighting is used. - boolean usingPerPixelLighting = prefs.getBoolean("perPixelLighting", false); - - // Whether or not texture filtering is being forced. - boolean isForcingTextureFiltering = prefs.getBoolean("forceTextureFiltering", false); - - // Whether or not fog is disabled. - boolean fogIsDisabled = prefs.getBoolean("disableFog", false); - - // Stereoscopy setting - String stereoscopyMode = prefs.getString("stereoscopyMode", "0"); - - // Stereoscopy swap eyes - boolean stereoscopyEyeSwap = prefs.getBoolean("stereoSwapEyes", false); - - // Stereoscopy separation - String stereoscopySeparation = prefs.getString("stereoDepth", "20"); - - // Stereoscopy convergence - String stereoConvergencePercentage = prefs.getString("stereoConvergencePercentage", "100"); - - // Controllers - // Controller 1 never gets disconnected due to touch screen - //boolean enableController1 = prefs.getBoolean("enableController1", true); - boolean enableController2 = prefs.getBoolean("enableController2", false); - boolean enableController3 = prefs.getBoolean("enableController3", false); - boolean enableController4 = prefs.getBoolean("enableController4", false); - - // CPU related Settings - NativeLibrary.SetConfig("Dolphin.ini", "Core", "CPUCore", currentEmuCore); - NativeLibrary.SetConfig("Dolphin.ini", "Core", "CPUThread", isUsingDualCore ? "True" : "False"); - - NativeLibrary.SetConfig("Dolphin.ini", "Core", "OverclockEnable", overclockEnabled ? "True" : "False"); - NativeLibrary.SetConfig("Dolphin.ini", "Core", "Overclock", overclockSetting); - - // GameCube analog ranges Setup - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad1", "Main Stick/Radius", currentMainRadius0); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad1", "C-Stick/Radius", currentCStickRadius0); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad1", "Triggers/Threshold", currentInputThres0); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad2", "Main Stick/Radius", currentMainRadius1); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad2", "C-Stick/Radius", currentCStickRadius1); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad2", "Triggers/Threshold", currentInputThres1); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad3", "Main Stick/Radius", currentMainRadius2); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad3", "C-Stick/Radius", currentCStickRadius2); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad3", "Triggers/Threshold", currentInputThres2); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad4", "Main Stick/Radius", currentMainRadius3); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad4", "C-Stick/Radius", currentCStickRadius3); - NativeLibrary.SetConfig("GCPadNew.ini", "GCPad4", "Triggers/Threshold", currentInputThres3); - - // Wiimote analog ranges Setup - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Tilt/Modifier/Range", currentTiltRange4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Tilt/Modifier/Range", currentTiltRange5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Tilt/Modifier/Range", currentTiltRange6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Tilt/Modifier/Range", currentTiltRange7); - - // Nunchuk analog ranges Setup - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Nunchuk/Stick/Radius", currentNunchukRadius4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Nunchuk/Stick/Radius", currentNunchukRange4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Nunchuk/Stick/Radius", currentNunchukRadius5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Nunchuk/Stick/Radius", currentNunchukRange5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Nunchuk/Stick/Radius", currentNunchukRadius6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Nunchuk/Stick/Radius", currentNunchukRange6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Nunchuk/Stick/Radius", currentNunchukRadius7); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Nunchuk/Stick/Radius", currentNunchukRange7); - - // Classic analog ranges Setup - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Classic/Left Stick/Radius", currentClassicLRadius4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Classic/Right Stick/Radius", currentClassicRRadius4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Classic/Triggers/Threshold", currentClassicThres4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Classic/Left Stick/Radius", currentClassicLRadius5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Classic/Right Stick/Radius", currentClassicRRadius5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Classic/Triggers/Threshold", currentClassicThres5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Classic/Left Stick/Radius", currentClassicLRadius6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Classic/Right Stick/Radius", currentClassicRRadius6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Classic/Triggers/Threshold", currentClassicThres6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Classic/Left Stick/Radius", currentClassicLRadius7); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Classic/Right Stick/Radius", currentClassicRRadius7); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Classic/Triggers/Threshold", currentClassicThres7); - - // Guitar analog ranges Setup - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Guitar/Stick/Radius", currentGuitarRadius4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Guitar/Stick/Radius", currentGuitarRadius5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Guitar/Stick/Radius", currentGuitarRadius6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Guitar/Stick/Radius", currentGuitarRadius7); - - // Drums analog ranges Setup - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Drums/Stick/Radius", currentDrumsRadius4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Drums/Stick/Radius", currentDrumsRadius5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Drums/Stick/Radius", currentDrumsRadius6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Drums/Stick/Radius", currentDrumsRadius7); - - // Turntable analog ranges Setup - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Turntable/Stick/Radius", currentTurntableRadius4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Turntable/Stick/Radius", currentTurntableRadius5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Turntable/Stick/Radius", currentTurntableRadius6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Turntable/Stick/Radius", currentTurntableRadius7); - - // Wiimote Extension Settings - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Extension", currentWiimoteExtension4); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote2", "Extension", currentWiimoteExtension5); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote3", "Extension", currentWiimoteExtension6); - NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote4", "Extension", currentWiimoteExtension7); - - // General Video Settings - NativeLibrary.SetConfig("Dolphin.ini", "Core", "GFXBackend", currentVideoBackend); - NativeLibrary.SetConfig("GFX.ini", "Settings", "ShowFPS", showingFPS ? "True" : "False"); - NativeLibrary.SetConfig("Dolphin.ini", "Android", "ScreenControls", drawingOnscreenControls ? "True" : "False"); - - // Video Hack Settings - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBAccessEnable", skipEFBAccess ? "False" : "True"); - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBEmulateFormatChanges", ignoreFormatChanges ? "True" : "False"); - NativeLibrary.SetConfig("GFX.ini", "Settings", "AspectRatio", aspectRatio); - - // Set EFB Copy Method - if (efbCopyMethod.equals("Off")) - { - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBCopyEnable", "False"); - } - else if (efbCopyMethod.equals("Texture")) - { - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBCopyEnable", "True"); - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBToTextureEnable", "True"); - } - else if (efbCopyMethod.equals("RAM (uncached)")) - { - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBCopyEnable", "True"); - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBToTextureEnable", "False"); - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBCopyCacheEnable", "False"); - } - else if (efbCopyMethod.equals("RAM (cached)")) - { - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBCopyEnable", "True"); - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBToTextureEnable", "False"); - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBCopyCacheEnable", "True"); - } - - // Set texture cache accuracy - NativeLibrary.SetConfig("GFX.ini", "Settings", "SafeTextureCacheColorSamples", textureCacheAccuracy); - - // Set external frame buffer. - if (externalFrameBuffer.equals("Disabled")) - { - NativeLibrary.SetConfig("GFX.ini", "Settings", "UseXFB", "False"); - } - else if (externalFrameBuffer.equals("Virtual")) - { - NativeLibrary.SetConfig("GFX.ini", "Settings", "UseXFB", "True"); - NativeLibrary.SetConfig("GFX.ini", "Settings", "UseRealXFB", "False"); - } - else if (externalFrameBuffer.equals("Real")) - { - NativeLibrary.SetConfig("GFX.ini", "Settings", "UseXFB", "True"); - NativeLibrary.SetConfig("GFX.ini", "Settings", "UseRealXFB", "True"); - } - - NativeLibrary.SetConfig("GFX.ini", "Settings", "FastDepthCalc", useFastDepthCalc ? "True" : "False"); - - //-- Enhancement Settings --// - NativeLibrary.SetConfig("GFX.ini", "Settings", "EFBScale", internalResolution); - NativeLibrary.SetConfig("GFX.ini", "Settings", "MSAA", FSAALevel); - NativeLibrary.SetConfig("GFX.ini", "Enhancements", "MaxAnisotropy", anisotropicFiltLevel); - NativeLibrary.SetConfig("GFX.ini", "Enhancements", "PostProcessingShader", postProcessing); - NativeLibrary.SetConfig("GFX.ini", "Hacks", "EFBScaledCopy", usingScaledEFBCopy ? "True" : "False"); - NativeLibrary.SetConfig("GFX.ini", "Settings", "EnablePixelLighting", usingPerPixelLighting ? "True" : "False"); - NativeLibrary.SetConfig("GFX.ini", "Enhancements", "ForceFiltering", isForcingTextureFiltering ? "True" : "False"); - NativeLibrary.SetConfig("GFX.ini", "Settings", "DisableFog", fogIsDisabled ? "True" : "False"); - NativeLibrary.SetConfig("GFX.ini", "Stereoscopy", "StereoMode", stereoscopyMode); - NativeLibrary.SetConfig("GFX.ini", "Stereoscopy", "StereoSwapEyes", stereoscopyEyeSwap ? "True" : "False"); - NativeLibrary.SetConfig("GFX.ini", "Stereoscopy", "StereoDepth", stereoscopySeparation); - NativeLibrary.SetConfig("GFX.ini", "Stereoscopy", "StereoConvergence", stereoConvergencePercentage); - NativeLibrary.SetConfig("Dolphin.ini", "Core", "SIDevice0", "6"); - NativeLibrary.SetConfig("Dolphin.ini", "Core", "SIDevice1", enableController2 ? "6" : "0"); - NativeLibrary.SetConfig("Dolphin.ini", "Core", "SIDevice2", enableController3 ? "6" : "0"); - NativeLibrary.SetConfig("Dolphin.ini", "Core", "SIDevice3", enableController4 ? "6" : "0"); - } -} diff --git a/Source/Android/app/src/main/res/drawable-hdpi/ic_settings.png b/Source/Android/app/src/main/res/drawable-hdpi/ic_settings.png deleted file mode 100644 index f9a8915fd2..0000000000 Binary files a/Source/Android/app/src/main/res/drawable-hdpi/ic_settings.png and /dev/null differ diff --git a/Source/Android/app/src/main/res/drawable-hdpi/ic_settings_core.png b/Source/Android/app/src/main/res/drawable-hdpi/ic_settings_core.png new file mode 100644 index 0000000000..27985eba15 Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-hdpi/ic_settings_core.png differ diff --git a/Source/Android/app/src/main/res/drawable-hdpi/ic_settings_graphics.png b/Source/Android/app/src/main/res/drawable-hdpi/ic_settings_graphics.png new file mode 100644 index 0000000000..8264acb8cd Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-hdpi/ic_settings_graphics.png differ diff --git a/Source/Android/app/src/main/res/drawable-xhdpi/ic_settings.png b/Source/Android/app/src/main/res/drawable-xhdpi/ic_settings.png deleted file mode 100644 index 12e5d100dd..0000000000 Binary files a/Source/Android/app/src/main/res/drawable-xhdpi/ic_settings.png and /dev/null differ diff --git a/Source/Android/app/src/main/res/drawable-xhdpi/ic_settings_core.png b/Source/Android/app/src/main/res/drawable-xhdpi/ic_settings_core.png new file mode 100644 index 0000000000..c6fae13587 Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-xhdpi/ic_settings_core.png differ diff --git a/Source/Android/app/src/main/res/drawable-xhdpi/ic_settings_graphics.png b/Source/Android/app/src/main/res/drawable-xhdpi/ic_settings_graphics.png new file mode 100644 index 0000000000..de130a60fb Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-xhdpi/ic_settings_graphics.png differ diff --git a/Source/Android/app/src/main/res/drawable-xxhdpi/ic_settings.png b/Source/Android/app/src/main/res/drawable-xxhdpi/ic_settings.png deleted file mode 100644 index 6bb8f6e080..0000000000 Binary files a/Source/Android/app/src/main/res/drawable-xxhdpi/ic_settings.png and /dev/null differ diff --git a/Source/Android/app/src/main/res/drawable-xxhdpi/ic_settings_core.png b/Source/Android/app/src/main/res/drawable-xxhdpi/ic_settings_core.png new file mode 100644 index 0000000000..4c5f2493a5 Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-xxhdpi/ic_settings_core.png differ diff --git a/Source/Android/app/src/main/res/drawable-xxhdpi/ic_settings_graphics.png b/Source/Android/app/src/main/res/drawable-xxhdpi/ic_settings_graphics.png new file mode 100644 index 0000000000..7b7e2f7a1a Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-xxhdpi/ic_settings_graphics.png differ diff --git a/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_settings.png b/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_settings.png deleted file mode 100644 index 97e9ca945c..0000000000 Binary files a/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_settings.png and /dev/null differ diff --git a/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_settings_core.png b/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_settings_core.png new file mode 100644 index 0000000000..9c8fde1b43 Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_settings_core.png differ diff --git a/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_settings_graphics.png b/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_settings_graphics.png new file mode 100644 index 0000000000..d7b1ac95ce Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_settings_graphics.png differ diff --git a/Source/Android/app/src/main/res/drawable/ic_settings_core_tv.png b/Source/Android/app/src/main/res/drawable/ic_settings_core_tv.png new file mode 100644 index 0000000000..9c8fde1b43 Binary files /dev/null and b/Source/Android/app/src/main/res/drawable/ic_settings_core_tv.png differ diff --git a/Source/Android/app/src/main/res/drawable/ic_settings_graphics_tv.png b/Source/Android/app/src/main/res/drawable/ic_settings_graphics_tv.png new file mode 100644 index 0000000000..d7b1ac95ce Binary files /dev/null and b/Source/Android/app/src/main/res/drawable/ic_settings_graphics_tv.png differ diff --git a/Source/Android/app/src/main/res/drawable/ic_settings_tv.png b/Source/Android/app/src/main/res/drawable/ic_settings_tv.png deleted file mode 100644 index 9e242e7748..0000000000 Binary files a/Source/Android/app/src/main/res/drawable/ic_settings_tv.png and /dev/null differ diff --git a/Source/Android/app/src/main/res/layout/activity_settings.xml b/Source/Android/app/src/main/res/layout/activity_settings.xml index d72e5a0142..492fbf04ec 100644 --- a/Source/Android/app/src/main/res/layout/activity_settings.xml +++ b/Source/Android/app/src/main/res/layout/activity_settings.xml @@ -1,20 +1,5 @@ - - - - - - - \ No newline at end of file + diff --git a/Source/Android/app/src/main/res/layout/dialog_seekbar.xml b/Source/Android/app/src/main/res/layout/dialog_seekbar.xml new file mode 100644 index 0000000000..4d81af8d81 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/dialog_seekbar.xml @@ -0,0 +1,37 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/layout/fragment_settings.xml b/Source/Android/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000000..3823d50d12 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/layout/list_item_setting.xml b/Source/Android/app/src/main/res/layout/list_item_setting.xml new file mode 100644 index 0000000000..d150d53265 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/list_item_setting.xml @@ -0,0 +1,39 @@ + + + + + + + + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/layout/list_item_setting_checkbox.xml b/Source/Android/app/src/main/res/layout/list_item_setting_checkbox.xml new file mode 100644 index 0000000000..265c51bb61 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/list_item_setting_checkbox.xml @@ -0,0 +1,49 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/layout/list_item_settings_header.xml b/Source/Android/app/src/main/res/layout/list_item_settings_header.xml new file mode 100644 index 0000000000..3c72913c93 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/list_item_settings_header.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/layout/slider_layout.xml b/Source/Android/app/src/main/res/layout/slider_layout.xml deleted file mode 100644 index a667ff6489..0000000000 --- a/Source/Android/app/src/main/res/layout/slider_layout.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Source/Android/app/src/main/res/menu/menu_game_grid.xml b/Source/Android/app/src/main/res/menu/menu_game_grid.xml index 5b6bf23ac0..a87f89f32d 100644 --- a/Source/Android/app/src/main/res/menu/menu_game_grid.xml +++ b/Source/Android/app/src/main/res/menu/menu_game_grid.xml @@ -7,9 +7,14 @@ android:icon="@drawable/ic_refresh" app:showAsAction="ifRoom"/> + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/values/arrays.xml b/Source/Android/app/src/main/res/values/arrays.xml index cf812c87e9..c7299780c6 100644 --- a/Source/Android/app/src/main/res/values/arrays.xml +++ b/Source/Android/app/src/main/res/values/arrays.xml @@ -9,77 +9,49 @@ @string/jit64_recompiler @string/jitil_recompiler - + 0 1 2 - + @string/interpreter @string/jit_arm_recompiler - + 0 3 - + @string/interpreter @string/jit_arm64_recompiler - + 0 4 - + @string/interpreter - + 0 - + @string/interpreter @string/cached_interpreter - + 0 5 - - - - - @string/software_renderer - @string/opengl_es3 - - - Software Renderer - OGL - - - - - @string/software_renderer - @string/opengl - - - Software Renderer - OGL - - - - - @string/software_renderer - - - Software Renderer - + @@ -99,57 +71,17 @@ Turntable - - - @string/disabled - @string/efb_copy_texture - @string/efb_copy_ram_uncached - @string/efb_copy_ram_cached - - - Off - Texture - RAM (cached) - RAM (uncached) - - @string/texture_cache_accuracy_low @string/texture_cache_accuracy_medium @string/texture_cache_accuracy_high - + 128 512 0 - - - - - 100 - 90 - 80 - 70 - 60 - 50 - 40 - 30 - 20 - 10 - - - 100,000000 - 90,000000 - 80,000000 - 70,000000 - 60,000000 - 50,000000 - 40,000000 - 30,000000 - 20,000000 - 10,000000 - + @@ -157,11 +89,11 @@ @string/external_frame_buffer_virtual @string/external_frame_buffer_real - - Disabled - Virtual - Real - + + 0 + 1 + 2 + @@ -174,7 +106,7 @@ 5x Native (3200x2640) 6x Native (3840x3168) for 4K - + 2 3 4 @@ -183,7 +115,7 @@ 7 8 9 - + @@ -191,11 +123,11 @@ 2x 4x - + 0 1 2 - + @@ -205,13 +137,13 @@ 8x 16x - + 0 1 2 3 4 - + @@ -220,12 +152,12 @@ Top-and-Bottom Anaglyph - + 0 1 2 3 - + @@ -234,12 +166,12 @@ Force 4:3 Stretch To Window - + 0 1 2 3 - + Europe diff --git a/Source/Android/app/src/main/res/values/dimens.xml b/Source/Android/app/src/main/res/values/dimens.xml index 986b230792..ba98e10607 100644 --- a/Source/Android/app/src/main/res/values/dimens.xml +++ b/Source/Android/app/src/main/res/values/dimens.xml @@ -9,4 +9,11 @@ 16dp 128dp + + 2dp + 4dp + 8dp + 12dp + 16dp + 32dp diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 7819de79cc..cf98d53bb9 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -286,11 +286,8 @@ Ignore any requests from the CPU to read/write to the EFB. Ignore Format Changes Ignore any changes to the EFB format. - EFB Copy Method - Determines how EFB copies will be emulated. - Texture - RAM (uncached) - RAM (cached) + Store EFB Copies to Texture Only + Stores EFB Copies exclusively on the GPU, bypassing system memory. Causes graphical defects in a small number of games. If unsure, leave this checked. Texture Cache Texture Cache Accuracy The safer the selection, the less likely the emulator will be missing any texture updates from RAM. @@ -320,7 +317,8 @@ - Settings + Settings + Video Settings Refresh Library @@ -334,6 +332,8 @@ Extension Bindings Video Settings Emulation Activity + OK + Cancel Toggle Touch Controls diff --git a/Source/Android/app/src/main/res/values/styles.xml b/Source/Android/app/src/main/res/values/styles.xml index dba853f492..d5db92e1e3 100644 --- a/Source/Android/app/src/main/res/values/styles.xml +++ b/Source/Android/app/src/main/res/values/styles.xml @@ -17,7 +17,7 @@ - diff --git a/Source/Android/app/src/main/res/xml/preferences.xml b/Source/Android/app/src/main/res/xml/preferences.xml deleted file mode 100644 index 3a5bc32422..0000000000 --- a/Source/Android/app/src/main/res/xml/preferences.xml +++ /dev/null @@ -1,4834 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -