diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java index 56c82d96..67a606f6 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java @@ -1,9 +1,11 @@ package com.panda3ds.pandroid.app; import android.os.Bundle; + import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import com.panda3ds.pandroid.R; + +import com.google.android.material.color.DynamicColors; import com.panda3ds.pandroid.data.config.GlobalConfig; @@ -28,5 +30,8 @@ public class BaseActivity extends AppCompatActivity { private void applyTheme() { currentTheme = PandroidApplication.getThemeId(); setTheme(currentTheme); + if (GlobalConfig.get(GlobalConfig.KEY_APP_THEME) == GlobalConfig.THEME_ANDROID){ + DynamicColors.applyToActivityIfAvailable(this); + } } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java index 946ef883..3ae5d66d 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java @@ -3,6 +3,7 @@ package com.panda3ds.pandroid.app; import android.content.Intent; import android.os.Build; import android.os.Bundle; +import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -16,17 +17,22 @@ import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.app.game.AlberInputListener; import com.panda3ds.pandroid.app.game.DrawerFragment; +import com.panda3ds.pandroid.app.game.EmulatorListener; import com.panda3ds.pandroid.data.config.GlobalConfig; import com.panda3ds.pandroid.input.InputHandler; import com.panda3ds.pandroid.input.InputMap; import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.view.PandaGlSurfaceView; import com.panda3ds.pandroid.view.PandaLayoutController; +import com.panda3ds.pandroid.view.ds.DsLayoutManager; +import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; import com.panda3ds.pandroid.view.utils.PerformanceView; -public class GameActivity extends BaseActivity { +public class GameActivity extends BaseActivity implements EmulatorListener { private final DrawerFragment drawerFragment = new DrawerFragment(); - private final AlberInputListener inputListener = new AlberInputListener(this::onBackPressed); + private final AlberInputListener inputListener = new AlberInputListener(this); + private ConsoleRenderer renderer; + private int currentDsLayout; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -43,6 +49,8 @@ public class GameActivity extends BaseActivity { PandaGlSurfaceView pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.ACTIVITY_PARAMETER_PATH)); setContentView(R.layout.game_activity); + renderer = pandaSurface.getRenderer(); + ((FrameLayout) findViewById(R.id.panda_gl_frame)) .addView(pandaSurface, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); @@ -104,6 +112,12 @@ public class GameActivity extends BaseActivity { } } + @Override + public void switchDualScreenLayout() { + currentDsLayout = currentDsLayout + 1 < DsLayoutManager.getLayoutCount() ? currentDsLayout + 1 : 0; + renderer.setLayout(DsLayoutManager.createLayout(currentDsLayout)); + } + @Override public boolean dispatchGenericMotionEvent(MotionEvent ev) { if ((!drawerFragment.isOpened()) && InputHandler.processMotionEvent(ev)) { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java index 5e03e516..8f677c3e 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java @@ -2,6 +2,7 @@ package com.panda3ds.pandroid.app; import android.os.Bundle; import android.view.MenuItem; + import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -16,15 +17,25 @@ public class MainActivity extends BaseActivity implements NavigationBarView.OnIt private final GamesFragment gamesFragment = new GamesFragment(); private final SearchFragment searchFragment = new SearchFragment(); private final SettingsFragment settingsFragment = new SettingsFragment(); + private NavigationBarView navigationBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - NavigationBarView bar = findViewById(R.id.navigation); - bar.setOnItemSelectedListener(this); - bar.postDelayed(() -> bar.setSelectedItemId(bar.getSelectedItemId()), 5); + navigationBar = findViewById(R.id.navigation); + navigationBar.setOnItemSelectedListener(this); + navigationBar.postDelayed(() -> navigationBar.setSelectedItemId(navigationBar.getSelectedItemId()), 5); + } + + @Override + public void onBackPressed() { + if (navigationBar.getSelectedItemId() != R.id.games){ + navigationBar.setSelectedItemId(R.id.games); + return; + } + super.onBackPressed(); } @Override diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java index 4f5c5761..f7f12d0f 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java @@ -7,7 +7,6 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.SwitchPreference; import com.panda3ds.pandroid.lang.Function; diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java index ec15a9bb..bc848a8c 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java @@ -11,8 +11,8 @@ import java.util.Objects; public class AlberInputListener implements Function { - private final Runnable backListener; - public AlberInputListener(Runnable backListener) { this.backListener = backListener; } + private final EmulatorListener emulator; + public AlberInputListener(EmulatorListener emulator) { this.emulator = emulator; } private final Vector2 axis = new Vector2(0.0f, 0.0f); @@ -21,7 +21,7 @@ public class AlberInputListener implements Function { KeyName key = InputMap.relative(event.getName()); if (Objects.equals(event.getName(), "KEYCODE_BACK")) { - backListener.run(); + emulator.onBackPressed(); return; } @@ -48,6 +48,11 @@ public class AlberInputListener implements Function { axis.x = event.getValue(); axisChanged = true; break; + case CHANGE_DS_LAYOUT: + if (!event.isDown()){ + emulator.switchDualScreenLayout(); + } + break; default: if (event.isDown()) { AlberDriver.KeyDown(key.getKeyId()); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/DrawerFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/DrawerFragment.java index a1fa9eec..25bcc9ae 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/DrawerFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/DrawerFragment.java @@ -1,6 +1,7 @@ package com.panda3ds.pandroid.app.game; import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.LayoutInflater; import android.view.MenuItem; @@ -42,13 +43,21 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe super.onViewCreated(view, savedInstanceState); drawerContainer.setVisibility(View.GONE); - GameMetadata game = GameUtils.getCurrentGame(); + ((NavigationView)view.findViewById(R.id.menu)).setNavigationItemSelectedListener(this); + refresh(); + } - ((GameIconView)view.findViewById(R.id.game_icon)).setImageBitmap(game.getIcon()); + private void refresh(){ + GameMetadata game = GameUtils.getCurrentGame(); + View view = getView(); + if (game.getIcon() != null && !game.getIcon().isRecycled()) { + ((GameIconView) view.findViewById(R.id.game_icon)).setImageBitmap(game.getIcon()); + } else { + ((GameIconView) view.findViewById(R.id.game_icon)).setImageDrawable(new ColorDrawable(Color.TRANSPARENT)); + } ((AppCompatTextView)view.findViewById(R.id.game_title)).setText(game.getTitle()); ((AppCompatTextView)view.findViewById(R.id.game_publisher)).setText(game.getPublisher()); - ((NavigationView)view.findViewById(R.id.menu)).setNavigationItemSelectedListener(this); } @Override @@ -72,6 +81,7 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe drawerContainer.setVisibility(View.VISIBLE); drawerContainer.open(); drawerContainer.postDelayed(this::refreshLayout, 20); + refresh(); } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/EmulatorListener.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/EmulatorListener.java new file mode 100644 index 00000000..5c0f7f63 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/EmulatorListener.java @@ -0,0 +1,7 @@ +package com.panda3ds.pandroid.app.game; + +public interface EmulatorListener { + void onBackPressed(); + + void switchDualScreenLayout(); +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java index 4ac73661..e49eb2b4 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java @@ -1,13 +1,15 @@ package com.panda3ds.pandroid.app.main; +import android.content.Context; import android.os.Bundle; import androidx.annotation.Nullable; import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.PandroidApplication; import com.panda3ds.pandroid.app.PreferenceActivity; import com.panda3ds.pandroid.app.base.BasePreferenceFragment; -import com.panda3ds.pandroid.app.preferences.AppearancePreferences; +import com.panda3ds.pandroid.app.preferences.GeneralPreferences; import com.panda3ds.pandroid.app.preferences.AdvancedPreferences; import com.panda3ds.pandroid.app.preferences.InputPreferences; @@ -15,8 +17,18 @@ public class SettingsFragment extends BasePreferenceFragment { @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { setPreferencesFromResource(R.xml.start_preferences, rootKey); + findPreference("application").setSummary(getVersionName()); setItemClick("input", (item) -> PreferenceActivity.launch(requireContext(), InputPreferences.class)); - setItemClick("appearance", (item)-> PreferenceActivity.launch(requireContext(), AppearancePreferences.class)); + setItemClick("general", (item)-> PreferenceActivity.launch(requireContext(), GeneralPreferences.class)); setItemClick("advanced", (item)-> PreferenceActivity.launch(requireContext(), AdvancedPreferences.class)); } + + private String getVersionName() { + try { + Context context = PandroidApplication.getAppContext(); + return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; + } catch (Exception e){ + return "???"; + } + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AppearancePreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AppearancePreferences.java deleted file mode 100644 index 04c89d9a..00000000 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AppearancePreferences.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.panda3ds.pandroid.app.preferences; - -import android.os.Bundle; - -import androidx.annotation.Nullable; - -import com.panda3ds.pandroid.R; -import com.panda3ds.pandroid.app.BaseActivity; -import com.panda3ds.pandroid.app.base.BasePreferenceFragment; -import com.panda3ds.pandroid.data.config.GlobalConfig; -import com.panda3ds.pandroid.view.preferences.SingleSelectionPreferences; - -public class AppearancePreferences extends BasePreferenceFragment { - @Override - public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { - setPreferencesFromResource(R.xml.appearance_preference, rootKey); - - setActivityTitle(R.string.appearance); - - SingleSelectionPreferences themePreference = findPreference("theme"); - themePreference.setSelectedItem(GlobalConfig.get(GlobalConfig.KEY_APP_THEME)); - themePreference.setOnPreferenceChangeListener((preference, value) -> { - GlobalConfig.set(GlobalConfig.KEY_APP_THEME, (int) value); - return false; - }); - } -} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GeneralPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GeneralPreferences.java new file mode 100644 index 00000000..7ecfbcad --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/GeneralPreferences.java @@ -0,0 +1,20 @@ +package com.panda3ds.pandroid.app.preferences; + +import android.os.Bundle; + +import androidx.annotation.Nullable; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.PreferenceActivity; +import com.panda3ds.pandroid.app.base.BasePreferenceFragment; +import com.panda3ds.pandroid.app.preferences.ds.DsListPreferences; + +public class GeneralPreferences extends BasePreferenceFragment { + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + setPreferencesFromResource(R.xml.general_preference, rootKey); + setItemClick("appearance.theme", (pref) -> new ThemeSelectorDialog(requireActivity()).show()); + setItemClick("appearance.ds", (pref) -> PreferenceActivity.launch(requireActivity(), DsListPreferences.class)); + setActivityTitle(R.string.general); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ThemeSelectorDialog.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ThemeSelectorDialog.java new file mode 100644 index 00000000..b53f673d --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ThemeSelectorDialog.java @@ -0,0 +1,79 @@ +package com.panda3ds.pandroid.app.preferences; + +import android.app.Activity; +import android.content.Context; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatRadioButton; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.data.config.GlobalConfig; +import com.panda3ds.pandroid.view.recycler.AutoFitGridLayout; +import com.panda3ds.pandroid.view.recycler.SimpleListAdapter; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.function.Predicate; + +public class ThemeSelectorDialog extends BottomSheetDialog { + + private final SimpleListAdapter adapter; + private final int currentTheme = GlobalConfig.get(GlobalConfig.KEY_APP_THEME); + private static final Theme[] themes = { + new Theme(R.style.Theme_Pandroid, R.string.theme_device, GlobalConfig.THEME_ANDROID), + new Theme(R.style.Theme_Pandroid_Light, R.string.light, GlobalConfig.THEME_LIGHT), + new Theme(R.style.Theme_Pandroid_Dark, R.string.dark, GlobalConfig.THEME_DARK), + new Theme(R.style.Theme_Pandroid_Black, R.string.black, GlobalConfig.THEME_BLACK) + }; + + + public ThemeSelectorDialog(@NonNull Context context) { + super(context); + View content = LayoutInflater.from(context).inflate(R.layout.dialog_select_theme, null, false); + setContentView(content); + adapter = new SimpleListAdapter<>(R.layout.hold_theme_preview_summary, this::bindItemView); + adapter.clear(); + ArrayList themeList = new ArrayList<>(Arrays.asList(themes)); + themeList.sort((o1, o2) -> o1.id == currentTheme ? -1 : 0); + adapter.addAll(themeList); + + ((RecyclerView) content.findViewById(R.id.recycler)).setAdapter(adapter); + ((RecyclerView) content.findViewById(R.id.recycler)).setLayoutManager(new AutoFitGridLayout(getContext(), 150)); + } + + private void bindItemView(int i, Theme theme, View view) { + ViewGroup container = view.findViewById(R.id.preview); + container.removeAllViews(); + container.addView(LayoutInflater.from(new ContextThemeWrapper(getContext(), theme.style)).inflate(R.layout.hold_theme_preview, null, false)); + ((TextView) view.findViewById(R.id.title)).setText(theme.name); + ((AppCompatRadioButton) view.findViewById(R.id.checkbox)).setChecked(GlobalConfig.get(GlobalConfig.KEY_APP_THEME) == theme.id); + view.setOnClickListener(v -> { + dismiss(); + if (theme.id != GlobalConfig.get(GlobalConfig.KEY_APP_THEME)) { + GlobalConfig.set(GlobalConfig.KEY_APP_THEME, theme.id); + ((Activity) v.getContext()).recreate(); + } + }); + } + + private static final class Theme { + private final int style; + private final int name; + private final int id; + + private Theme(int style, int name, int value) { + this.style = style; + this.name = name; + this.id = value; + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ds/DsEditorPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ds/DsEditorPreferences.java new file mode 100644 index 00000000..aa0cdc56 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ds/DsEditorPreferences.java @@ -0,0 +1,51 @@ +package com.panda3ds.pandroid.app.preferences.ds; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.BaseActivity; +import com.panda3ds.pandroid.view.ds.DsEditorView; +import com.panda3ds.pandroid.view.ds.DsLayoutManager; + +public class DsEditorPreferences extends Fragment { + private LinearLayout layout; + private DsEditorView editor; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + layout = new LinearLayout(container.getContext()); + return layout; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + int index = getArguments().getInt("index"); + layout.removeAllViews(); + layout.addView(editor = new DsEditorView(view.getContext(), index), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + ((BaseActivity)requireActivity()).getSupportActionBar().hide(); + } + + @Override + public void onResume() { + super.onResume(); + requireActivity().getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN|View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_IMMERSIVE); + } + + @Override + public void onDestroy() { + super.onDestroy(); + DsLayoutManager.save(); + Toast.makeText(requireActivity(), R.string.saved, Toast.LENGTH_LONG).show(); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ds/DsListPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ds/DsListPreferences.java new file mode 100644 index 00000000..cede2d13 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ds/DsListPreferences.java @@ -0,0 +1,42 @@ +package com.panda3ds.pandroid.app.preferences.ds; + +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.PreferenceActivity; +import com.panda3ds.pandroid.app.base.BasePreferenceFragment; +import com.panda3ds.pandroid.view.ds.DsLayoutManager; + +public class DsListPreferences extends BasePreferenceFragment { + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + setPreferencesFromResource(R.xml.empty_preferences, rootKey); + setActivityTitle(R.string.dual_screen_layouts); + refresh(); + } + + public void refresh(){ + PreferenceScreen screen = getPreferenceScreen(); + screen.removeAll(); + for (int i = 0; i < DsLayoutManager.getLayoutCount(); i++){ + Preference pref = new Preference(getPreferenceScreen().getContext()); + pref.setIconSpaceReserved(false); + pref.setTitle("Layout "+(i+1)); + pref.setSummary(R.string.click_to_change); + pref.setIcon(R.drawable.ic_edit); + pref.setKey(String.valueOf(i)); + + final int index = i; + pref.setOnPreferenceClickListener(preference -> { + PreferenceActivity.launch(requireContext(), DsEditorPreferences.class, new Intent().putExtra("index", index)); + return false; + }); + screen.addPreference(pref); + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java index 0fde3d2f..eef15d3a 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java @@ -1,5 +1,7 @@ package com.panda3ds.pandroid.data; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.panda3ds.pandroid.lang.Task; @@ -30,7 +32,10 @@ public class GsonConfigParser { String[] content = new String[] {"{}"}; new Task(()->{ if (FileUtils.exists(getPath())) { - content[0] = FileUtils.readTextFile(getPath()); + String src = FileUtils.readTextFile(getPath()); + if(src != null && src.length() > 2){ + content[0] = src; + } } }).runSync(); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java index bff1f9e0..4445486e 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java @@ -1,5 +1,7 @@ package com.panda3ds.pandroid.data.config; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import com.google.gson.internal.LinkedTreeMap; import com.panda3ds.pandroid.data.GsonConfigParser; import com.panda3ds.pandroid.utils.Constants; @@ -10,6 +12,7 @@ import java.util.Map; public class GlobalConfig { private static final GsonConfigParser parser = new GsonConfigParser(Constants.PREF_GLOBAL_CONFIG); + private static final Gson gson = new Gson(); public static final int THEME_ANDROID = 0; public static final int THEME_LIGHT = 1; @@ -23,6 +26,7 @@ public class GlobalConfig { public static final Key KEY_LOGGER_SERVICE = new Key<>("dev.loggerService", false); public static final Key KEY_APP_THEME = new Key<>("app.theme", THEME_ANDROID); public static final Key KEY_SCREEN_GAMEPAD_VISIBLE = new Key<>("app.screen_gamepad.visible", true); + public static final Key KEY_DS_LAYOUTS = new Key<>("app.ds.layouts", ""); public static void initialize() { data = parser.load(DataModel.class); @@ -54,6 +58,21 @@ public class GlobalConfig { writeChanges(); } + public static T getExtra(Key key, Class dataClass){ + if (data.extras.has(key.name)){ + return gson.fromJson(data.extras.getAsJsonObject(key.name), dataClass); + } + return gson.fromJson("{}", dataClass); + } + + public static synchronized void putExtra(Key key, Object value){ + if (data.extras.has(key.name)){ + data.extras.remove(key.name); + } + data.extras.add(key.name, gson.toJsonTree(value)); + writeChanges(); + } + private static void writeChanges() { parser.save(data); } @@ -70,6 +89,7 @@ public class GlobalConfig { private static class DataModel { private final Map configs = new LinkedTreeMap<>(); + private final JsonObject extras = new JsonObject(); public Object get(String key) { return configs.get(key); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java index 390708b9..28edd426 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java @@ -25,6 +25,7 @@ public class InputHandler { }; private static final HashMap motionDownEvents = new HashMap<>(); + private static final HashMap keyDownEvents = new HashMap<>(); private static boolean containsSource(int[] sources, int sourceMask) { for (int source : sources) { @@ -108,8 +109,17 @@ public class InputHandler { return true; } } - - handleEvent(new InputEvent(KeyEvent.keyCodeToString(event.getKeyCode()), event.getAction() == KeyEvent.ACTION_UP ? 0.0f : 1.0f)); + String code = KeyEvent.keyCodeToString(event.getKeyCode()); + if (event.getAction() == KeyEvent.ACTION_UP){ + keyDownEvents.remove(code); + handleEvent(new InputEvent(code, 0.0f)); + } else if (!keyDownEvents.containsKey(code)){ + keyDownEvents.put(code, new InputEvent(code, 1.0f)); + } + for (InputEvent env: keyDownEvents.values()){ + handleEvent(env); + } + return true; } @@ -117,5 +127,6 @@ public class InputHandler { eventListener = null; motionDeadZone = 0.0f; motionDownEvents.clear(); + keyDownEvents.clear(); } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/KeyName.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/KeyName.java index 1253529f..24c420d3 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/KeyName.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/KeyName.java @@ -19,7 +19,8 @@ public enum KeyName { SELECT(Constants.INPUT_KEY_SELECT), L(Constants.INPUT_KEY_L), R(Constants.INPUT_KEY_R), - NULL; + NULL, + CHANGE_DS_LAYOUT; private final int keyId; diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/math/Shape.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/math/Shape.java new file mode 100644 index 00000000..1f956241 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/math/Shape.java @@ -0,0 +1,32 @@ +package com.panda3ds.pandroid.math; + +public class Shape { + public int x = 0, y = 0, width = 1, height = 1; + + public Shape() { + } + + public Shape(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public void move(int x, int y) { + this.x += x; + this.y += y; + } + + public void normalize() { + this.x = Math.max(x, 0); + this.y = Math.max(y, 0); + this.width = Math.max(width, 1); + this.height = Math.max(height, 1); + } + + public void maxSize(int w, int h) { + this.width = Math.max(w, this.width); + this.height = Math.max(h, this.height); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java index 76dc5e7d..b4c1c4b9 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java @@ -2,24 +2,26 @@ package com.panda3ds.pandroid.view; import static android.opengl.GLES32.*; +import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.opengl.GLSurfaceView; import android.os.Handler; import android.util.Log; -import android.widget.Toast; -import androidx.appcompat.app.AlertDialog; + import com.panda3ds.pandroid.AlberDriver; +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.base.BottomAlertDialog; import com.panda3ds.pandroid.data.SMDH; import com.panda3ds.pandroid.data.config.GlobalConfig; import com.panda3ds.pandroid.data.game.GameMetadata; import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.utils.GameUtils; import com.panda3ds.pandroid.utils.PerformanceMonitor; +import com.panda3ds.pandroid.view.ds.DsLayoutManager; import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; -import com.panda3ds.pandroid.view.renderer.layout.DefaultScreenLayout; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; @@ -38,7 +40,7 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels; - setLayout(new DefaultScreenLayout()); + setLayout(DsLayoutManager.createLayout(0)); } @Override @@ -74,9 +76,9 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer glGenTextures(1, generateBuffer, 0); screenTexture = generateBuffer[0]; glBindTexture(GL_TEXTURE_2D, screenTexture); - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, screenWidth, screenHeight); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, Constants.N3DS_WIDTH, Constants.N3DS_FULL_HEIGHT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); glGenFramebuffers(1, generateBuffer, 0); @@ -95,19 +97,17 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer if (!AlberDriver.LoadRom(romPath)) { // Get a handler that can be used to post to the main thread Handler mainHandler = new Handler(context.getMainLooper()); - - Runnable runnable = new Runnable() { - @Override - public void run() { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle("Failed to load ROM") - .setMessage("Make sure it's a valid 3DS ROM and that storage permissions are configured properly.") - .setPositiveButton("OK", null) + mainHandler.post(()-> { + new BottomAlertDialog(context) + .setTitle(R.string.failed_load_rom) + .setMessage(R.string.dialog_message_invalid_rom) + .setPositiveButton(android.R.string.ok, (dialog, witch) -> { + dialog.dismiss(); + ((Activity) context).finish(); + }) .setCancelable(false) .show(); - } - }; - mainHandler.post(runnable); + }); GameMetadata game = GameUtils.getCurrentGame(); GameUtils.removeGame(game); @@ -131,6 +131,9 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer } public void onDrawFrame(GL10 unused) { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (AlberDriver.HasRomLoaded()) { AlberDriver.RunFrame(screenFbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/DsEditorView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/DsEditorView.java new file mode 100644 index 00000000..ae98faa7 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/DsEditorView.java @@ -0,0 +1,397 @@ +package com.panda3ds.pandroid.view.ds; + +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.DashPathEffect; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import androidx.appcompat.widget.AppCompatSpinner; +import androidx.appcompat.widget.AppCompatTextView; + +import com.google.android.material.checkbox.MaterialCheckBox; +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.math.Shape; +import com.panda3ds.pandroid.math.Vector2; +import com.panda3ds.pandroid.utils.Constants; + +public class DsEditorView extends FrameLayout { + + private static final int COLOR_TOP_SELECTION = Color.RED; + private static final int COLOR_BOTTOM_SELECTION = Color.BLUE; + private final float SIZE_DP; + + private final Paint selectionPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final DsLayout layout; + private int width = 1, height = 1; + private final LinearLayout gravityAnchor; + private final LinearLayout aspectRatioFixLayout; + private final LinearLayout modeSelectorLayout; + private final AppCompatSpinner modeSelector; + private final PointView spacePoint; + private final PointView topDisplay; + private final PointView bottomDisplay; + private final PointView topDisplayResizer; + private final PointView bottomDisplayResizer; + + @SuppressLint("ClickableViewAccessibility") + public DsEditorView(Context context, int index) { + super(context); + layout = (DsLayout) DsLayoutManager.createLayout(index); + + SIZE_DP = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()); + + selectionPaint.setColor(COLOR_TOP_SELECTION); + selectionPaint.setStrokeWidth(SIZE_DP * 2); + selectionPaint.setPathEffect(new DashPathEffect(new float[]{SIZE_DP * 10, SIZE_DP * 10}, 0.0f)); + selectionPaint.setStyle(Paint.Style.STROKE); + + layout.setTopDisplaySourceSize(Constants.N3DS_WIDTH, Constants.N3DS_HALF_HEIGHT); + layout.setBottomDisplaySourceSize(Constants.N3DS_WIDTH - 40 - 40, Constants.N3DS_HALF_HEIGHT); + setBackgroundColor(Color.argb(2, 0, 0, 0)); + + LayoutInflater inflater = LayoutInflater.from(context); + + gravityAnchor = (LinearLayout) inflater.inflate(R.layout.ds_editor_gravity_anchor, this, false); + gravityAnchor.findViewById(R.id.up).setOnClickListener(v -> { + layout.getCurrentModel().gravity = Gravity.TOP; + refreshLayout(); + }); + gravityAnchor.findViewById(R.id.center).setOnClickListener(v -> { + layout.getCurrentModel().gravity = Gravity.CENTER; + refreshLayout(); + }); + gravityAnchor.findViewById(R.id.down).setOnClickListener(v -> { + layout.getCurrentModel().gravity = Gravity.BOTTOM; + refreshLayout(); + }); + gravityAnchor.findViewById(R.id.revert).setOnClickListener(v -> { + layout.getCurrentModel().reverse = !layout.getCurrentModel().reverse; + refreshLayout(); + }); + + { + modeSelectorLayout = (LinearLayout) inflater.inflate(R.layout.ds_editor_spinner, this, false); + ArrayAdapter spinnerAdapter = new ArrayAdapter<>(getContext(), R.layout.ds_editor_spinner_label); + spinnerAdapter.addAll("SINGLE", "RELATIVE", "ABSOLUTE"); + modeSelector = modeSelectorLayout.findViewById(R.id.spinner); + modeSelector.setAdapter(spinnerAdapter); + modeSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + layout.getCurrentModel().mode = Mode.values()[position]; + refreshLayout(); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + } + + { + aspectRatioFixLayout = (LinearLayout) inflater.inflate(R.layout.ds_editor_lock_aspect, this, false); + ((MaterialCheckBox) aspectRatioFixLayout.findViewById(R.id.checkbox)).setOnCheckedChangeListener((buttonView, checked) -> { + layout.getCurrentModel().lockAspect = checked; + if (checked) { + fixAspect(); + } + refreshPoints(); + }); + } + + spacePoint = new PointView(); + spacePoint.setColor(Color.WHITE, COLOR_TOP_SELECTION); + spacePoint.setOnTouchListener((view, motion) -> { + layout.getCurrentModel().space = (motion.getX() + spacePoint.x()) / (float) width; + refreshPoints(); + return true; + }); + + spacePoint.setLayoutGravity(Gravity.START | Gravity.CENTER); + + setOnClickListener(v -> { + if (layout.getCurrentModel().mode == Mode.SINGLE) { + layout.getCurrentModel().singleTop = !layout.getCurrentModel().singleTop; + refreshPoints(); + } + }); + + topDisplay = new PointView(); + topDisplay.setText(R.string.top_display); + topDisplay.setOnTouchListener(new DisplayTouchEvent(true)); + topDisplay.setBackground(new SelectionDrawable(COLOR_TOP_SELECTION)); + + bottomDisplay = new PointView(); + bottomDisplay.setText(R.string.bottom_display); + bottomDisplay.setOnTouchListener(new DisplayTouchEvent(false)); + bottomDisplay.setBackground(new SelectionDrawable(COLOR_BOTTOM_SELECTION)); + + topDisplayResizer = new PointView(); + topDisplayResizer.setColor(0, COLOR_TOP_SELECTION); + topDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(true)); + + bottomDisplayResizer = new PointView(); + bottomDisplayResizer.setColor(0, COLOR_BOTTOM_SELECTION); + bottomDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(false)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + if (this.width != width || this.height != height) { + this.width = width; + this.height = height; + refreshLayout(); + } + } + + private void fixAspect() { + Shape top = layout.getCurrentModel().preferredTop; + Shape bottom = layout.getCurrentModel().preferredBottom; + + top.height = (int) (((float) top.width / Constants.N3DS_WIDTH) * Constants.N3DS_HALF_HEIGHT); + bottom.height = (int) (((float) bottom.width / (Constants.N3DS_WIDTH - 80)) * Constants.N3DS_HALF_HEIGHT); + } + + private void refreshPoints() { + Model data = layout.getCurrentModel(); + layout.update(width, height); + Rect bottomDisplay = layout.getBottomDisplayBounds(); + Rect topDisplay = layout.getTopDisplayBounds(); + + switch (data.mode) { + case RELATIVE: { + if (width > height) { + Rect primaryDisplay = data.reverse ? bottomDisplay : topDisplay; + data.space = primaryDisplay.width() / (float) width; + spacePoint.setCenterPosition(primaryDisplay.width(), (int) (SIZE_DP * 15)); + spacePoint.setText(String.valueOf((int) (data.space * 100))); + } + } + break; + case SINGLE: + case ABSOLUTE: { + } + break; + } + + data.preferredTop.maxSize((int) (SIZE_DP * 64), (int) (SIZE_DP * 64)); + data.preferredBottom.maxSize((int) (SIZE_DP * 64), (int) (SIZE_DP * 64)); + + this.topDisplay.setSize(topDisplay.width(), topDisplay.height()); + this.topDisplay.setPosition(topDisplay.left, topDisplay.top); + + this.bottomDisplay.setSize(bottomDisplay.width(), bottomDisplay.height()); + this.bottomDisplay.setPosition(bottomDisplay.left, bottomDisplay.top); + + if (data.lockAspect) { + topDisplayResizer.setCenterPosition(topDisplay.right, topDisplay.top + (topDisplay.height() / 2)); + bottomDisplayResizer.setCenterPosition(bottomDisplay.right, bottomDisplay.top + (bottomDisplay.height() / 2)); + } else { + topDisplayResizer.setCenterPosition(topDisplay.right, topDisplay.bottom); + bottomDisplayResizer.setCenterPosition(bottomDisplay.right, bottomDisplay.bottom); + } + + invalidate(); + } + + private void refreshLayout() { + removeAllViews(); + layout.update(width, height); + boolean landscape = width > height; + addView(topDisplay); + addView(bottomDisplay); + + gravityAnchor.setOrientation(LinearLayout.HORIZONTAL); + addView(modeSelectorLayout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER)); + switch (layout.getCurrentModel().mode) { + case RELATIVE: { + addView(gravityAnchor, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.CENTER | Gravity.TOP)); + if (landscape) { + addView(spacePoint); + } + } + break; + case ABSOLUTE: { + addView(aspectRatioFixLayout, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.CENTER | Gravity.TOP)); + addView(topDisplayResizer); + addView(bottomDisplayResizer); + } + break; + case SINGLE: { + addView(aspectRatioFixLayout, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.CENTER | Gravity.TOP)); + } + break; + } + ((MaterialCheckBox) aspectRatioFixLayout.findViewById(R.id.checkbox)).setChecked(layout.getCurrentModel().lockAspect); + + modeSelector.setSelection(layout.getCurrentModel().mode.ordinal()); + gravityAnchor.findViewById(R.id.revert).setRotation(landscape ? 0 : 90); + refreshPoints(); + } + + private class PointView extends AppCompatTextView { + + public PointView() { + super(DsEditorView.this.getContext()); + setLayoutParams(new FrameLayout.LayoutParams((int) (SIZE_DP * 30), (int) (SIZE_DP * 30))); + setBackgroundResource(R.drawable.medium_card_background); + setGravity(Gravity.CENTER); + this.setFocusable(true); + this.setClickable(true); + } + + public int x() { + return ((LayoutParams) getLayoutParams()).leftMargin; + } + + public int y() { + return ((LayoutParams) getLayoutParams()).topMargin; + } + + public int width() { + return ((LayoutParams) getLayoutParams()).width; + } + + public void setColor(int text, int background) { + setTextColor(text); + setBackgroundTintList(ColorStateList.valueOf(background)); + } + + public void setSize(int width, int height) { + LayoutParams params = (LayoutParams) getLayoutParams(); + params.width = Math.max(0, width); + params.height = Math.max(0, height); + setLayoutParams(params); + } + + public void setPosition(int x, int y) { + LayoutParams params = (LayoutParams) getLayoutParams(); + params.leftMargin = x; + params.topMargin = y; + setLayoutParams(params); + } + + public void setCenterPosition(int x, int y) { + int middle = this.width() / 2; + setPosition(Math.max(-middle, Math.min(x - middle, width - middle)), Math.max(-middle, Math.min(y - middle, height - middle))); + } + + public void setLayoutGravity(int gravity) { + FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams(); + params.gravity = gravity; + setLayoutParams(params); + } + } + + private class DisplayTouchEvent implements OnTouchListener { + private final boolean topScreen; + private final Vector2 inner = new Vector2(0, 0); + private Vector2 downEvent = null; + + private DisplayTouchEvent(boolean topScreen) { + this.topScreen = topScreen; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + Shape preferred = topScreen ? layout.getCurrentModel().preferredTop : layout.getCurrentModel().preferredBottom; + if (layout.getCurrentModel().mode == Mode.ABSOLUTE) { + PointView point = (PointView) v; + if (event.getAction() != MotionEvent.ACTION_UP) { + if (downEvent == null) { + downEvent = new Vector2(event.getRawX(), event.getRawY()); + inner.set(point.x(), point.y()); + return true; + } + + preferred.x = (int) ((event.getRawX() - downEvent.x) + inner.x); + preferred.y = (int) ((event.getRawY() - downEvent.y) + inner.y); + preferred.normalize(); + + refreshPoints(); + + return true; + } + + downEvent = null; + return false; + } else if (layout.getCurrentModel().mode == Mode.SINGLE && event.getAction() == MotionEvent.ACTION_UP) { + callOnClick(); + } + return false; + } + } + + private class DisplayResizeTouchEvent implements OnTouchListener { + private final boolean topScreen; + private final Vector2 size = new Vector2(0, 0); + private Vector2 downEvent = null; + + private DisplayResizeTouchEvent(boolean topScreen) { + this.topScreen = topScreen; + } + + + @Override + public boolean onTouch(View v, MotionEvent event) { + Shape preferred = topScreen ? layout.getCurrentModel().preferredTop : layout.getCurrentModel().preferredBottom; + if (event.getAction() != MotionEvent.ACTION_UP) { + if (downEvent == null) { + downEvent = new Vector2(event.getRawX(), event.getRawY()); + size.set(preferred.width, preferred.height); + return true; + } + + preferred.width = (int) (size.x + ((event.getRawX() - downEvent.x))); + + if (layout.getCurrentModel().lockAspect) { + fixAspect(); + } else { + preferred.height = (int) (size.y + ((event.getRawY() - downEvent.y))); + } + preferred.maxSize((int) (SIZE_DP * 32), (int) (SIZE_DP * 32)); + refreshPoints(); + return true; + } + downEvent = null; + return false; + } + } + + private class SelectionDrawable extends ColorDrawable { + private final Paint solidPaint = new Paint(); + + public SelectionDrawable(int color) { + super(color); + } + + @Override + public void draw(Canvas canvas) { + int color = this.getColor(); + selectionPaint.setColor(color); + solidPaint.setColor(Color.argb(50, Color.red(color), Color.green(color), Color.blue(color))); + canvas.drawRect(this.getBounds(), solidPaint); + canvas.drawRect(this.getBounds(), selectionPaint); + } + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/DsLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/DsLayout.java new file mode 100644 index 00000000..9f0ea2e5 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/DsLayout.java @@ -0,0 +1,183 @@ +package com.panda3ds.pandroid.view.ds; + +import android.graphics.Rect; +import android.view.Gravity; + +import com.panda3ds.pandroid.math.Shape; +import com.panda3ds.pandroid.math.Vector2; +import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; + +class DsLayout implements ConsoleLayout { + private final Rect topDisplay = new Rect(); + private final Rect bottomDisplay = new Rect(); + + private final Vector2 screenSize = new Vector2(0,0); + private final Vector2 sourceTop = new Vector2(0,0); + private final Vector2 sourceBottom = new Vector2(0,0); + private final Model[] modes = new Model[2]; + + public DsLayout(Model landscape, Model portrait){ + modes[0] = landscape; + modes[1] = portrait; + } + + public DsLayout(){ + this(new Model(), new Model()); + } + + @Override + public void update(int screenWidth, int screenHeight) { + screenSize.set(screenWidth, screenHeight); + update(); + } + + @Override + public void setBottomDisplaySourceSize(int width, int height) { + sourceBottom.set(width, height); + update(); + } + + @Override + public void setTopDisplaySourceSize(int width, int height) { + sourceTop.set(width, height); + update(); + } + + @Override + public Rect getBottomDisplayBounds() { + return bottomDisplay; + } + + @Override + public Rect getTopDisplayBounds() { + return topDisplay; + } + + public void update(){ + Model data = getCurrentModel(); + Mode mode = data.mode; + switch (mode){ + case RELATIVE: + relative(data); + break; + case SINGLE: + single(data); + break; + case ABSOLUTE: + absolute(data); + break; + } + } + + private void absolute(Model data) { + Shape top = data.preferredTop; + Shape bottom = data.preferredBottom; + + top.normalize(); + bottom.normalize(); + + topDisplay.set(top.x, top.y, top.x +top.width, top.y + top.height); + bottomDisplay.set(bottom.x, bottom.y, bottom.x +bottom.width, bottom.y + bottom.height); + } + + /** + * SINGLE LAYOUT: + * SHOW ONLY SCREEN IN FIT MODE + */ + private void single(Model data) { + Vector2 source = data.singleTop ? sourceTop : sourceBottom; + Rect dest = data.singleTop ? topDisplay : bottomDisplay; + + if (data.lockAspect) { + int x = 0, y = 0; + int width = (int) ((screenSize.y / source.y) * source.x); + int height; + + if (width > screenSize.x) { + height = (int) ((screenSize.x / source.x) * source.y); + width = (int) screenSize.x; + y = (int) ((screenSize.y - height) / 2); + } else { + height = (int) screenSize.y; + x = (int) ((screenSize.x - width) / 2); + } + dest.set(x,y,x+width,y+height); + } else { + dest.set(0,0, (int) screenSize.x, (int) screenSize.y); + } + (data.singleTop ? bottomDisplay : topDisplay).set(0,0,0,0); + } + + + /*** + * RELATIVE LAYOUT: + * ORGANIZE SCREEN IN POSITION BASED IN GRAVITY + * AND SPACE, THE SPACE DETERMINE LANDSCAPE TOP SCREEN SIZE + */ + private void relative(Model data) { + int screenWidth = (int) screenSize.x; + int screenHeight = (int) screenSize.y; + + Vector2 topSourceSize = this.sourceTop; + Vector2 bottomSourceSize = this.sourceBottom; + + Rect topDisplay = this.topDisplay; + Rect bottomDisplay = this.bottomDisplay; + + if (data.reverse){ + topSourceSize = this.sourceBottom; + bottomSourceSize = this.sourceTop; + + topDisplay = this.bottomDisplay; + bottomDisplay = this.topDisplay; + } + + if (screenWidth > screenHeight) { + int topDisplayWidth = (int) ((screenHeight / topSourceSize.y) * topSourceSize.x); + int topDisplayHeight = screenHeight; + + if (topDisplayWidth > (screenWidth * data.space)) { + topDisplayWidth = (int) (screenWidth * data.space); + topDisplayHeight = (int) ((topDisplayWidth / topSourceSize.x) * topSourceSize.y); + } + + int bottomDisplayHeight = (int) (((screenWidth - topDisplayWidth) / bottomSourceSize.x) * bottomSourceSize.y); + + topDisplay.set(0, 0, topDisplayWidth, topDisplayHeight); + bottomDisplay.set(topDisplayWidth, 0, topDisplayWidth + (screenWidth - topDisplayWidth), bottomDisplayHeight); + + switch (data.gravity){ + case Gravity.CENTER:{ + bottomDisplay.offset(0, (screenHeight-bottomDisplay.height())/2); + topDisplay.offset(0, (screenHeight-topDisplay.height())/2); + }break; + case Gravity.BOTTOM:{ + bottomDisplay.offset(0, (screenHeight-bottomDisplay.height())); + topDisplay.offset(0, (screenHeight-topDisplay.height())); + }break; + } + + } else { + int topScreenHeight = (int) ((screenWidth / topSourceSize.x) * topSourceSize.y); + topDisplay.set(0, 0, screenWidth, topScreenHeight); + + int bottomDisplayHeight = (int) ((screenWidth / bottomSourceSize.x) * bottomSourceSize.y); + int bottomDisplayWidth = screenWidth; + int bottomDisplayX = 0; + + if (topScreenHeight + bottomDisplayHeight > screenHeight) { + bottomDisplayHeight = (screenHeight - topScreenHeight); + bottomDisplayWidth = (int) ((bottomDisplayHeight / bottomSourceSize.y) * bottomSourceSize.x); + bottomDisplayX = (screenWidth - bottomDisplayX) / 2; + } + + topDisplay.set(0, 0, screenWidth, topScreenHeight); + bottomDisplay.set(bottomDisplayX, topScreenHeight, bottomDisplayX + bottomDisplayWidth, topScreenHeight + bottomDisplayHeight); + } + } + + public Model getCurrentModel() { + return screenSize.x > screenSize.y ? modes[0] : modes[1]; + } + +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/DsLayoutManager.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/DsLayoutManager.java new file mode 100644 index 00000000..4f651e11 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/DsLayoutManager.java @@ -0,0 +1,52 @@ +package com.panda3ds.pandroid.view.ds; + +import com.panda3ds.pandroid.data.config.GlobalConfig; +import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; + +import java.util.ArrayList; + +public class DsLayoutManager { + private static final DataModel data; + + static { + data = GlobalConfig.getExtra(GlobalConfig.KEY_DS_LAYOUTS, DataModel.class); + if (data.models.size() == 0){ + setupBasicModels(); + } + } + + private static void setupBasicModels() { + Model model1 = new Model(); + + Model model2 = new Model(); + model2.mode = Mode.SINGLE; + model2.singleTop = false; + + Model model3 = new Model(); + model3.mode = Mode.SINGLE; + model3.singleTop = true; + + data.models.add(new Model[]{model1, model1.clone()}); + data.models.add(new Model[]{model2, model2.clone()}); + data.models.add(new Model[]{model3, model3.clone()}); + + save(); + } + + public static synchronized void save(){ + GlobalConfig.putExtra(GlobalConfig.KEY_DS_LAYOUTS, data); + } + + public static int getLayoutCount(){ + return data.models.size(); + } + + public static ConsoleLayout createLayout(int index){ + index = Math.min(getLayoutCount()-1, index); + return new DsLayout(data.models.get(index)[0],data.models.get(index)[1]); + } + + private static class DataModel { + private final ArrayList models = new ArrayList<>(); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/Mode.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/Mode.java new file mode 100644 index 00000000..0a31f62d --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/Mode.java @@ -0,0 +1,7 @@ +package com.panda3ds.pandroid.view.ds; + +enum Mode { + SINGLE, + RELATIVE, + ABSOLUTE +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/Model.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/Model.java new file mode 100644 index 00000000..e2cf0c8a --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/ds/Model.java @@ -0,0 +1,31 @@ +package com.panda3ds.pandroid.view.ds; + +import android.util.Log; +import android.view.Gravity; + +import androidx.annotation.NonNull; + +import com.panda3ds.pandroid.math.Shape; +import com.panda3ds.pandroid.utils.Constants; + +class Model implements Cloneable { + public Mode mode = Mode.RELATIVE; + public final Shape preferredTop = new Shape(); + public final Shape preferredBottom = new Shape(); + public boolean reverse = false; + public boolean singleTop = true; + public float space = 0.6f; + public int gravity = Gravity.CENTER; + public boolean lockAspect = true; + + @NonNull + @Override + public Model clone() { + try { + return (Model) super.clone(); + } catch (Exception e){ + Log.e(Constants.LOG_TAG, "Error on clone DsModel!", e); + return new Model(); + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/preferences/SingleSelectionPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/preferences/SingleSelectionPreferences.java deleted file mode 100644 index 49fabd6a..00000000 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/preferences/SingleSelectionPreferences.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.panda3ds.pandroid.view.preferences; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.util.AttributeSet; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; - -import com.panda3ds.pandroid.R; -import com.panda3ds.pandroid.utils.Constants; - -public class SingleSelectionPreferences extends PreferenceCategory implements Preference.OnPreferenceClickListener { - private final Drawable transparent = new ColorDrawable(Color.TRANSPARENT); - private final Drawable doneDrawable = ContextCompat.getDrawable(getContext(), R.drawable.ic_done); - - public SingleSelectionPreferences(@NonNull Context context) { - super(context); - } - - public SingleSelectionPreferences(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public SingleSelectionPreferences(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public SingleSelectionPreferences(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - { - try { - TypedArray color = getContext().obtainStyledAttributes(new int[]{ - android.R.attr.textColorSecondary - }); - doneDrawable.setTint(color.getColor(0, Color.RED)); - color.recycle(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - color.close(); - } - } catch (Exception e) { - Log.e(Constants.LOG_TAG, "Error on obtain text color secondary: ", e); - } - } - - @Override - public void onAttached() { - super.onAttached(); - - for (int i = 0; i < getPreferenceCount();i++) { - getPreference(i).setOnPreferenceClickListener(this); - } - } - - public void setSelectedItem(int index) { - onPreferenceClick(getPreference(index)); - } - - @Override - public boolean onPreferenceClick(@NonNull Preference preference) { - int index = 0; - - for (int i = 0; i < getPreferenceCount(); i++) { - Preference item = getPreference(i); - if (item == preference) { - index = i; - item.setIcon(R.drawable.ic_done); - } else { - item.setIcon(transparent); - } - } - - callChangeListener(index); - return false; - } -} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/RelativeScreenLayout.java similarity index 50% rename from src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java rename to src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/RelativeScreenLayout.java index a726b2e6..29b826e6 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/RelativeScreenLayout.java @@ -1,10 +1,11 @@ package com.panda3ds.pandroid.view.renderer.layout; import android.graphics.Rect; +import android.view.Gravity; import com.panda3ds.pandroid.math.Vector2; -public class DefaultScreenLayout implements ConsoleLayout { +public class RelativeScreenLayout implements ConsoleLayout { private final Rect topDisplay = new Rect(); private final Rect bottomDisplay = new Rect(); @@ -12,6 +13,12 @@ public class DefaultScreenLayout implements ConsoleLayout { private final Vector2 topSourceSize = new Vector2(1.0f, 1.0f); private final Vector2 bottomSourceSize = new Vector2(1.0f, 1.0f); + private boolean landscapeReverse = false; + private boolean portraitReverse = false; + private float landscapeSpace = 0.6f; + private int landscapeGravity = Gravity.CENTER; + private int portraitGravity = Gravity.TOP; + @Override public void update(int screenWidth, int screenHeight) { screenSize.set(screenWidth, screenHeight); @@ -29,16 +36,50 @@ public class DefaultScreenLayout implements ConsoleLayout { updateBounds(); } + public void setPortraitGravity(int portraitGravity) { + this.portraitGravity = portraitGravity; + } + + public void setLandscapeGravity(int landscapeGravity) { + this.landscapeGravity = landscapeGravity; + } + + public void setLandscapeSpace(float landscapeSpace) { + this.landscapeSpace = landscapeSpace; + } + + public void setLandscapeReverse(boolean landscapeReverse) { + this.landscapeReverse = landscapeReverse; + } + + public void setPortraitReverse(boolean portraitReverse) { + this.portraitReverse = portraitReverse; + } + private void updateBounds() { int screenWidth = (int) screenSize.x; int screenHeight = (int) screenSize.y; + Vector2 topSourceSize = this.topSourceSize; + Vector2 bottomSourceSize = this.bottomSourceSize; + + Rect topDisplay = this.topDisplay; + Rect bottomDisplay = this.bottomDisplay; + + if ((landscapeReverse && screenWidth > screenHeight) || (portraitReverse && screenWidth < screenHeight)){ + topSourceSize = this.bottomSourceSize; + bottomSourceSize = this.topSourceSize; + + topDisplay = this.bottomDisplay; + bottomDisplay = this.topDisplay; + } + if (screenWidth > screenHeight) { int topDisplayWidth = (int) ((screenHeight / topSourceSize.y) * topSourceSize.x); int topDisplayHeight = screenHeight; - if (topDisplayWidth > (screenWidth * 0.7)) { - topDisplayWidth = (int) (screenWidth * 0.7); + if (topDisplayWidth > (screenWidth * landscapeSpace)) { + topDisplayWidth = (int) (screenWidth * landscapeSpace); topDisplayHeight = (int) ((topDisplayWidth / topSourceSize.x) * topSourceSize.y); } @@ -46,6 +87,7 @@ public class DefaultScreenLayout implements ConsoleLayout { topDisplay.set(0, 0, topDisplayWidth, topDisplayHeight); bottomDisplay.set(topDisplayWidth, 0, topDisplayWidth + (screenWidth - topDisplayWidth), bottomDisplayHeight); + adjustHorizontalGravity(); } else { int topScreenHeight = (int) ((screenWidth / topSourceSize.x) * topSourceSize.y); topDisplay.set(0, 0, screenWidth, topScreenHeight); @@ -62,9 +104,42 @@ public class DefaultScreenLayout implements ConsoleLayout { topDisplay.set(0, 0, screenWidth, topScreenHeight); bottomDisplay.set(bottomDisplayX, topScreenHeight, bottomDisplayX + bottomDisplayWidth, topScreenHeight + bottomDisplayHeight); + adjustVerticalGravity(); } } + private void adjustHorizontalGravity(){ + int topOffset = 0; + int bottomOffset = 0; + switch (landscapeGravity){ + case Gravity.CENTER:{ + topOffset = (int) (screenSize.y - topDisplay.height())/2; + bottomOffset = (int) (screenSize.y - bottomDisplay.height())/2; + }break; + case Gravity.BOTTOM:{ + topOffset = (int) (screenSize.y - topDisplay.height()); + bottomOffset = (int) (screenSize.y - bottomDisplay.height()); + }break; + } + topDisplay.offset(0, topOffset); + bottomDisplay.offset(0, bottomOffset); + } + + private void adjustVerticalGravity(){ + int height = (topDisplay.height() + bottomDisplay.height()); + int space = 0; + switch (portraitGravity){ + case Gravity.CENTER:{ + space = (int) (screenSize.y - height)/2; + }break; + case Gravity.BOTTOM:{ + space = (int) (screenSize.y - height); + }break; + } + topDisplay.offset(0, space); + bottomDisplay.offset(0,space); + } + @Override public Rect getBottomDisplayBounds() { return bottomDisplay; diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/SingleScreenLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/SingleScreenLayout.java new file mode 100644 index 00000000..96be64bb --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/SingleScreenLayout.java @@ -0,0 +1,63 @@ +package com.panda3ds.pandroid.view.renderer.layout; + +import android.graphics.Rect; +import android.view.Gravity; + +import com.panda3ds.pandroid.math.Vector2; + +public class SingleScreenLayout implements ConsoleLayout { + private final Rect topDisplay = new Rect(); + private final Rect bottomDisplay = new Rect(); + private final Vector2 screenSize = new Vector2(1.0f, 1.0f); + private final Vector2 topSourceSize = new Vector2(1.0f, 1.0f); + private final Vector2 bottomSourceSize = new Vector2(1.0f, 1.0f); + private boolean top = true; + + @Override + public void update(int screenWidth, int screenHeight) { + screenSize.set(screenWidth, screenHeight); + updateBounds(); + } + + @Override + public void setBottomDisplaySourceSize(int width, int height) { + bottomSourceSize.set(width, height); + updateBounds(); + } + @Override + public void setTopDisplaySourceSize(int width, int height) { + topSourceSize.set(width, height); + updateBounds(); + } + + private void updateBounds() { + int screenWidth = (int) screenSize.x; + int screenHeight = (int) screenSize.y; + Vector2 source = top ? topSourceSize : bottomSourceSize; + Rect dest = top ? topDisplay : bottomDisplay; + + int width = Math.round((screenHeight / source.y) * source.x); + int height = screenHeight; + int y = 0; + int x = (screenWidth - width) / 2; + if (width > screenWidth){ + width = screenWidth; + height = Math.round((screenWidth / source.x) * source.y); + x = 0; + y = (screenHeight - height)/2; + } + dest.set(x, y, x + width, y+height); + + (top ? bottomDisplay : topDisplay).set(0,0,0,0); + } + + @Override + public Rect getBottomDisplayBounds() { + return bottomDisplay; + } + + @Override + public Rect getTopDisplayBounds() { + return topDisplay; + } +} diff --git a/src/pandroid/app/src/main/res/drawable/alert_dialog_background.xml b/src/pandroid/app/src/main/res/drawable/alert_dialog_background.xml index 20a71e46..729e056b 100644 --- a/src/pandroid/app/src/main/res/drawable/alert_dialog_background.xml +++ b/src/pandroid/app/src/main/res/drawable/alert_dialog_background.xml @@ -3,6 +3,7 @@ + diff --git a/src/pandroid/app/src/main/res/drawable/ds_editor_popup_background.xml b/src/pandroid/app/src/main/res/drawable/ds_editor_popup_background.xml new file mode 100644 index 00000000..80fee9a8 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ds_editor_popup_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/ic_align_center.xml b/src/pandroid/app/src/main/res/drawable/ic_align_center.xml new file mode 100644 index 00000000..48b55b5e --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_align_center.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/ic_arrow_down.xml b/src/pandroid/app/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 00000000..3d879a2a --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/ic_arrow_up.xml b/src/pandroid/app/src/main/res/drawable/ic_arrow_up.xml new file mode 100644 index 00000000..0fe2fba3 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/ic_compare_arrow.xml b/src/pandroid/app/src/main/res/drawable/ic_compare_arrow.xml new file mode 100644 index 00000000..d11e9aa0 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_compare_arrow.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/ic_edit.xml b/src/pandroid/app/src/main/res/drawable/ic_edit.xml index 1c9bd3e6..6ddb5cb1 100644 --- a/src/pandroid/app/src/main/res/drawable/ic_edit.xml +++ b/src/pandroid/app/src/main/res/drawable/ic_edit.xml @@ -1,4 +1,4 @@ - diff --git a/src/pandroid/app/src/main/res/layout/dialog_select_theme.xml b/src/pandroid/app/src/main/res/layout/dialog_select_theme.xml new file mode 100644 index 00000000..ec0952fb --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/dialog_select_theme.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/ds_editor_gravity_anchor.xml b/src/pandroid/app/src/main/res/layout/ds_editor_gravity_anchor.xml new file mode 100644 index 00000000..3945b27a --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/ds_editor_gravity_anchor.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/ds_editor_lock_aspect.xml b/src/pandroid/app/src/main/res/layout/ds_editor_lock_aspect.xml new file mode 100644 index 00000000..238b86c5 --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/ds_editor_lock_aspect.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/ds_editor_spinner.xml b/src/pandroid/app/src/main/res/layout/ds_editor_spinner.xml new file mode 100644 index 00000000..ca74e81e --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/ds_editor_spinner.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/ds_editor_spinner_label.xml b/src/pandroid/app/src/main/res/layout/ds_editor_spinner_label.xml new file mode 100644 index 00000000..b852ab6f --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/ds_editor_spinner_label.xml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/fragment_game_drawer.xml b/src/pandroid/app/src/main/res/layout/fragment_game_drawer.xml index 8019c3c3..30b08386 100644 --- a/src/pandroid/app/src/main/res/layout/fragment_game_drawer.xml +++ b/src/pandroid/app/src/main/res/layout/fragment_game_drawer.xml @@ -69,26 +69,25 @@ - - - - - - - + + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="start"> + + + + + diff --git a/src/pandroid/app/src/main/res/layout/hold_theme_preview.xml b/src/pandroid/app/src/main/res/layout/hold_theme_preview.xml new file mode 100644 index 00000000..9bdab2f1 --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/hold_theme_preview.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/hold_theme_preview_summary.xml b/src/pandroid/app/src/main/res/layout/hold_theme_preview_summary.xml new file mode 100644 index 00000000..ade063bf --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/hold_theme_preview_summary.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml index 96a47941..a48c6999 100644 --- a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml @@ -56,4 +56,17 @@ Gráficos Carregando Rotacionar + Aplicar + Definir o tema do aplicativo + Tema do aplicativo + Falha ao carregar a ROM. + Falha ao carregar o arquivo ROM, por favor verifique se o arquivo não esta corrompido ou se o emulator tem permição para acessa-lo. + Sistema + Geral + Configurações gerais do emulador. + Tela inferior + Tela superior + Manter porporção + Disposições para as duas telas. + Altere as disposições disponiveis para as telas do console diff --git a/src/pandroid/app/src/main/res/values/strings.xml b/src/pandroid/app/src/main/res/values/strings.xml index 20e6c5c8..b06e037c 100644 --- a/src/pandroid/app/src/main/res/values/strings.xml +++ b/src/pandroid/app/src/main/res/values/strings.xml @@ -60,4 +60,18 @@ Use shader recompiler. Graphics Loading + Apply + Set application theme + Application theme + Failed to load ROM + Make sure it\'s a valid 3DS ROM and that storage permissions are configured properly. + System + General + General application configurations. + Bottom Display + Top Display + Fix aspect + Dual Screen layouts + Change layout of console screens. + Click to change diff --git a/src/pandroid/app/src/main/res/xml/appearance_preference.xml b/src/pandroid/app/src/main/res/xml/appearance_preference.xml deleted file mode 100644 index dd1ed483..00000000 --- a/src/pandroid/app/src/main/res/xml/appearance_preference.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/xml/empty_preferences.xml b/src/pandroid/app/src/main/res/xml/empty_preferences.xml new file mode 100644 index 00000000..624ed13a --- /dev/null +++ b/src/pandroid/app/src/main/res/xml/empty_preferences.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/xml/general_preference.xml b/src/pandroid/app/src/main/res/xml/general_preference.xml new file mode 100644 index 00000000..1d20c566 --- /dev/null +++ b/src/pandroid/app/src/main/res/xml/general_preference.xml @@ -0,0 +1,22 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/xml/input_map_preferences.xml b/src/pandroid/app/src/main/res/xml/input_map_preferences.xml index fbb6221f..43a64a90 100644 --- a/src/pandroid/app/src/main/res/xml/input_map_preferences.xml +++ b/src/pandroid/app/src/main/res/xml/input_map_preferences.xml @@ -133,6 +133,12 @@ app:summary="none" app:iconSpaceReserved="false"/> + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/xml/start_preferences.xml b/src/pandroid/app/src/main/res/xml/start_preferences.xml index f41e83a2..788000d6 100644 --- a/src/pandroid/app/src/main/res/xml/start_preferences.xml +++ b/src/pandroid/app/src/main/res/xml/start_preferences.xml @@ -4,6 +4,7 @@ android:divider="#F00">