Add game to launcher and some clear code.

This commit is contained in:
gabriel 2024-02-29 15:46:41 -04:00
parent 3a0ca3baa0
commit 94b9f3137d
39 changed files with 354 additions and 271 deletions

View file

@ -32,6 +32,8 @@
<activity <activity
android:name=".app.GameActivity" android:name=".app.GameActivity"
android:supportsPictureInPicture="true" android:supportsPictureInPicture="true"
android:taskAffinity="emulator.GameActivity"
android:launchMode="singleTop"
android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|density|uiMode"> android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|density|uiMode">
</activity> </activity>
<activity <activity
@ -57,6 +59,15 @@
</intent-filter> </intent-filter>
</provider> </provider>
<activity
android:name=".app.game.GameLauncher"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="pandroid-game"/>
</intent-filter>
</activity>
<service android:name=".app.services.LoggerService" android:process=":logger_service"/> <service android:name=".app.services.LoggerService" android:process=":logger_service"/>
</application> </application>
</manifest> </manifest>

View file

@ -1,8 +1,11 @@
package com.panda3ds.pandroid.app; package com.panda3ds.pandroid.app;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Rational;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
@ -11,11 +14,9 @@ import android.view.WindowManager;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import android.app.PictureInPictureParams;
import android.content.res.Configuration;
import android.util.DisplayMetrics;
import android.util.Rational;
import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.AlberDriver;
import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.game.AlberInputListener; import com.panda3ds.pandroid.app.game.AlberInputListener;
@ -61,9 +62,7 @@ public class GameActivity extends BaseActivity implements EmulatorCallback {
controllerLayout.initialize(); controllerLayout.initialize();
((CheckBox) findViewById(R.id.hide_screen_controller)).setOnCheckedChangeListener((buttonView, checked) -> { ((CheckBox) findViewById(R.id.hide_screen_controller)).setOnCheckedChangeListener((buttonView, checked) -> {
findViewById(R.id.overlay_controller).setVisibility(checked ? View.VISIBLE : View.GONE); changeOverlayVisibility(checked);
findViewById(R.id.overlay_controller).invalidate();
findViewById(R.id.overlay_controller).requestLayout();
GlobalConfig.set(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE, checked); GlobalConfig.set(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE, checked);
}); });
((CheckBox) findViewById(R.id.hide_screen_controller)).setChecked(GlobalConfig.get(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE)); ((CheckBox) findViewById(R.id.hide_screen_controller)).setChecked(GlobalConfig.get(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE));
@ -74,6 +73,13 @@ public class GameActivity extends BaseActivity implements EmulatorCallback {
PerformanceView view = new PerformanceView(this); PerformanceView view = new PerformanceView(this);
((FrameLayout) findViewById(R.id.panda_gl_frame)).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); ((FrameLayout) findViewById(R.id.panda_gl_frame)).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
} }
swapScreens(GlobalConfig.get(GlobalConfig.KEY_CURRENT_DS_LAYOUT));
}
private void changeOverlayVisibility(boolean visible){
findViewById(R.id.overlay_controller).setVisibility(visible ? View.VISIBLE : View.GONE);
findViewById(R.id.overlay_controller).invalidate();
findViewById(R.id.overlay_controller).requestLayout();
} }
@Override @Override
@ -84,51 +90,20 @@ public class GameActivity extends BaseActivity implements EmulatorCallback {
InputHandler.reset(); InputHandler.reset();
InputHandler.setMotionDeadZone(InputMap.getDeadZone()); InputHandler.setMotionDeadZone(InputMap.getDeadZone());
InputHandler.setEventListener(inputListener); InputHandler.setEventListener(inputListener);
if (GlobalConfig.get(GlobalConfig.KEY_PICTURE_IN_PICTURE)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!drawerFragment.isOpened()) {
setPictureInPictureParams(new PictureInPictureParams.Builder().setAutoEnterEnabled(true).build());
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
getTheme().applyStyle(R.style.GameActivityNavigationBar, true); getTheme().applyStyle(R.style.GameActivityNavigationBar, true);
} }
} }
@Override private void goToPictureInPicture() {
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
if (isInPictureInPictureMode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
findViewById(R.id.overlay_controller).setVisibility(View.GONE); builder.setAutoEnterEnabled(true);
} else { builder.setSeamlessResizeEnabled(true);
if (GlobalConfig.get(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE)) {
findViewById(R.id.overlay_controller).setVisibility(View.VISIBLE);
}
}
}
@Override
public void onUserLeaveHint() {
super.onUserLeaveHint();
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int widthPixels = displayMetrics.widthPixels;
int heightPixels = displayMetrics.heightPixels;
// Calculate aspect ratio
float aspectRatio = (float) widthPixels / (float) heightPixels;
if (GlobalConfig.get(GlobalConfig.KEY_PICTURE_IN_PICTURE)) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (!drawerFragment.isOpened()) {
Rational aspectRatioRational = new Rational(widthPixels, heightPixels);
PictureInPictureParams.Builder pipBuilder = new PictureInPictureParams.Builder();
pipBuilder.setAspectRatio(aspectRatioRational);
enterPictureInPictureMode(pipBuilder.build());
}
} }
builder.setAspectRatio(new Rational(10, 14));
enterPictureInPictureMode(builder.build());
} }
} }
@ -137,8 +112,14 @@ public class GameActivity extends BaseActivity implements EmulatorCallback {
super.onPause(); super.onPause();
InputHandler.reset(); InputHandler.reset();
if (GlobalConfig.get(GlobalConfig.KEY_PICTURE_IN_PICTURE)) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
goToPictureInPicture();
}
} else {
drawerFragment.open(); drawerFragment.open();
} }
}
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
@ -159,8 +140,9 @@ public class GameActivity extends BaseActivity implements EmulatorCallback {
} }
@Override @Override
public void swapScreens() { public void swapScreens(int index) {
currentDsLayout = currentDsLayout + 1 < DsLayoutManager.getLayoutCount() ? currentDsLayout + 1 : 0; currentDsLayout = index;
GlobalConfig.set(GlobalConfig.KEY_CURRENT_DS_LAYOUT,index);
renderer.setLayout(DsLayoutManager.createLayout(currentDsLayout)); renderer.setLayout(DsLayoutManager.createLayout(currentDsLayout));
} }
@ -176,11 +158,14 @@ public class GameActivity extends BaseActivity implements EmulatorCallback {
@Override @Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode); super.onPictureInPictureModeChanged(isInPictureInPictureMode);
changeOverlayVisibility(!isInPictureInPictureMode && GlobalConfig.get(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE));
findViewById(R.id.hide_screen_controller).setVisibility(isInPictureInPictureMode ? View.INVISIBLE : View.VISIBLE);
if (isInPictureInPictureMode){
getWindow().getDecorView().postDelayed(drawerFragment::close, 250);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S){
ActivityManager manager = ((ActivityManager) getSystemService(ACTIVITY_SERVICE));
manager.getAppTasks().forEach(ActivityManager.AppTask::moveToFront);
} }
@Override
protected void onUserLeaveHint() {
super.onUserLeaveHint();
} }
@Override @Override
@ -188,7 +173,6 @@ public class GameActivity extends BaseActivity implements EmulatorCallback {
if (AlberDriver.HasRomLoaded()) { if (AlberDriver.HasRomLoaded()) {
AlberDriver.Finalize(); AlberDriver.Finalize();
} }
super.onDestroy(); super.onDestroy();
} }
} }

View file

@ -7,6 +7,7 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreferenceCompat;
import com.panda3ds.pandroid.lang.Function; import com.panda3ds.pandroid.lang.Function;
@ -21,6 +22,10 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
}); });
} }
protected void setSwitchValue(String id, boolean value){
((SwitchPreferenceCompat)findPreference(id)).setChecked(value);
}
protected void setActivityTitle(@StringRes int titleId) { protected void setActivityTitle(@StringRes int titleId) {
ActionBar header = ((AppCompatActivity) requireActivity()).getSupportActionBar(); ActionBar header = ((AppCompatActivity) requireActivity()).getSupportActionBar();
if (header != null) { if (header != null) {

View file

@ -1,16 +1,23 @@
package com.panda3ds.pandroid.app.base; package com.panda3ds.pandroid.app.base;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.PandroidApplication;
import com.panda3ds.pandroid.app.game.GameLauncher;
import com.panda3ds.pandroid.data.game.GameMetadata; import com.panda3ds.pandroid.data.game.GameMetadata;
import com.panda3ds.pandroid.utils.CompatUtils;
import com.panda3ds.pandroid.utils.FileUtils; import com.panda3ds.pandroid.utils.FileUtils;
import com.panda3ds.pandroid.utils.GameUtils; import com.panda3ds.pandroid.utils.GameUtils;
import com.panda3ds.pandroid.view.gamesgrid.GameIconView; import com.panda3ds.pandroid.view.gamesgrid.GameIconView;
@ -32,11 +39,14 @@ public class GameAboutDialog extends BaseSheetDialog {
((TextView) findViewById(R.id.game_publisher)).setText(game.getPublisher()); ((TextView) findViewById(R.id.game_publisher)).setText(game.getPublisher());
((TextView) findViewById(R.id.region)).setText(game.getRegions()[0].localizedName()); ((TextView) findViewById(R.id.region)).setText(game.getRegions()[0].localizedName());
((TextView) findViewById(R.id.directory)).setText(FileUtils.obtainUri(game.getRealPath()).getPath()); ((TextView) findViewById(R.id.directory)).setText(FileUtils.obtainUri(game.getRealPath()).getPath());
findViewById(R.id.play).setOnClickListener(v -> { findViewById(R.id.play).setOnClickListener(v -> {
dismiss(); dismiss();
GameUtils.launch(getContext(), game); GameUtils.launch(getContext(), game);
}); });
findViewById(R.id.shortcut).setOnClickListener(v -> {
dismiss();
makeShortcut();
});
if (game.getRomPath().startsWith("folder:")) { if (game.getRomPath().startsWith("folder:")) {
findViewById(R.id.remove).setVisibility(View.GONE); findViewById(R.id.remove).setVisibility(View.GONE);
@ -50,4 +60,22 @@ public class GameAboutDialog extends BaseSheetDialog {
}); });
} }
} }
private void makeShortcut() {
Context context = CompatUtils.findActivity(getContext());
ShortcutInfoCompat.Builder shortcut = new ShortcutInfoCompat.Builder(context, game.getId());
if (game.getIcon() != null){
shortcut.setIcon(IconCompat.createWithAdaptiveBitmap(game.getIcon()));
} else {
shortcut.setIcon(IconCompat.createWithResource(getContext(), R.mipmap.ic_launcher));
}
shortcut.setActivity(new ComponentName(context, GameLauncher.class));
shortcut.setLongLabel(game.getTitle());
shortcut.setShortLabel(game.getTitle());
Intent intent = new Intent(PandroidApplication.getAppContext(), GameLauncher.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setData(new Uri.Builder().scheme("pandroid-game").authority(game.getId()).build());
shortcut.setIntent(intent);
ShortcutManagerCompat.requestPinShortcut(context,shortcut.build(),null);
}
} }

View file

@ -25,7 +25,9 @@ import com.panda3ds.pandroid.view.gamesgrid.GameIconView;
public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListener, NavigationView.OnNavigationItemSelectedListener { public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListener, NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout drawerContainer; private DrawerLayout drawerContainer;
private View drawerLayout;
private EmulatorCallback emulator; private EmulatorCallback emulator;
private GameMetadata game;
@Nullable @Nullable
@Override @Override
@ -44,21 +46,21 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
drawerContainer.setVisibility(View.GONE); drawerContainer.setVisibility(View.GONE);
drawerLayout = view.findViewById(R.id.drawer_layout);
((NavigationView)view.findViewById(R.id.menu)).setNavigationItemSelectedListener(this); ((NavigationView)view.findViewById(R.id.menu)).setNavigationItemSelectedListener(this);
refresh(); refresh();
} }
private void refresh(){ private void refresh(){
GameMetadata game = GameUtils.getCurrentGame(); game = GameUtils.getCurrentGame();
View view = getView();
if (game.getIcon() != null && !game.getIcon().isRecycled()) { if (game.getIcon() != null && !game.getIcon().isRecycled()) {
((GameIconView) view.findViewById(R.id.game_icon)).setImageBitmap(game.getIcon()); ((GameIconView) drawerLayout.findViewById(R.id.game_icon)).setImageBitmap(game.getIcon());
} else { } else {
((GameIconView) view.findViewById(R.id.game_icon)).setImageDrawable(new ColorDrawable(Color.TRANSPARENT)); ((GameIconView) drawerLayout.findViewById(R.id.game_icon)).setImageDrawable(new ColorDrawable(Color.TRANSPARENT));
} }
((AppCompatTextView)view.findViewById(R.id.game_title)).setText(game.getTitle()); ((AppCompatTextView)drawerLayout.findViewById(R.id.game_title)).setText(game.getTitle());
((AppCompatTextView)view.findViewById(R.id.game_publisher)).setText(game.getPublisher()); ((AppCompatTextView)drawerLayout.findViewById(R.id.game_publisher)).setText(game.getPublisher());
} }
@ -119,7 +121,7 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe
emulator.swapScreens(); emulator.swapScreens();
close(); close();
} else if (id == R.id.exit) { } else if (id == R.id.exit) {
requireActivity().finish(); requireActivity().finishAndRemoveTask();
} else if (id == R.id.lua_script) { } else if (id == R.id.lua_script) {
new LuaDialogFragment().show(getParentFragmentManager(), null); new LuaDialogFragment().show(getParentFragmentManager(), null);
} else if (id == R.id.change_orientation) { } else if (id == R.id.change_orientation) {

View file

@ -1,7 +1,11 @@
package com.panda3ds.pandroid.app.game; package com.panda3ds.pandroid.app.game;
import com.panda3ds.pandroid.data.config.GlobalConfig;
public interface EmulatorCallback { public interface EmulatorCallback {
void onBackPressed(); void onBackPressed();
void swapScreens(int index);
void swapScreens(); default void swapScreens() {
swapScreens(GlobalConfig.get(GlobalConfig.KEY_CURRENT_DS_LAYOUT)+1);
}
} }

View file

@ -0,0 +1,37 @@
package com.panda3ds.pandroid.app.game;
import android.net.Uri;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.content.pm.ShortcutManagerCompat;
import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.BaseActivity;
import com.panda3ds.pandroid.data.game.GameMetadata;
import com.panda3ds.pandroid.utils.GameUtils;
import java.util.Arrays;
public class GameLauncher extends BaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new TextView(this));
Uri uri = getIntent().getData();
if(uri != null && uri.getScheme().equals("pandroid-game")){
String gameId = uri.getAuthority();
GameMetadata game = GameUtils.findGameById(gameId);
if (game != null){
GameUtils.launch(this, game);
} else {
Toast.makeText(this, R.string.invalid_game,Toast.LENGTH_LONG).show();
ShortcutManagerCompat.removeDynamicShortcuts(this, Arrays.asList(gameId));
ShortcutManagerCompat.removeLongLivedShortcuts(this, Arrays.asList(gameId));
}
}
finish();
}
}

View file

@ -21,7 +21,6 @@ public class AdvancedPreferences extends BasePreferenceFragment {
setActivityTitle(R.string.advanced_options); setActivityTitle(R.string.advanced_options);
setItemClick("performanceMonitor", pref -> GlobalConfig.set(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY, ((SwitchPreferenceCompat) pref).isChecked())); setItemClick("performanceMonitor", pref -> GlobalConfig.set(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY, ((SwitchPreferenceCompat) pref).isChecked()));
setItemClick("pictureInPicture", pref -> GlobalConfig.set(GlobalConfig.KEY_PICTURE_IN_PICTURE, ((SwitchPreferenceCompat) pref).isChecked()));
setItemClick("shaderJit", pref -> GlobalConfig.set(GlobalConfig.KEY_SHADER_JIT, ((SwitchPreferenceCompat) pref).isChecked())); setItemClick("shaderJit", pref -> GlobalConfig.set(GlobalConfig.KEY_SHADER_JIT, ((SwitchPreferenceCompat) pref).isChecked()));
setItemClick("loggerService", pref -> { setItemClick("loggerService", pref -> {
boolean checked = ((SwitchPreferenceCompat) pref).isChecked(); boolean checked = ((SwitchPreferenceCompat) pref).isChecked();
@ -45,14 +44,7 @@ public class AdvancedPreferences extends BasePreferenceFragment {
private void refresh() { private void refresh() {
((SwitchPreferenceCompat) findPreference("performanceMonitor")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)); ((SwitchPreferenceCompat) findPreference("performanceMonitor")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY));
((SwitchPreferenceCompat) findPreference("pictureInPicture")).setChecked(GlobalConfig.get(GlobalConfig.KEY_PICTURE_IN_PICTURE));
((SwitchPreferenceCompat) findPreference("loggerService")).setChecked(GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE)); ((SwitchPreferenceCompat) findPreference("loggerService")).setChecked(GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE));
((SwitchPreferenceCompat) findPreference("shaderJit")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT)); ((SwitchPreferenceCompat) findPreference("shaderJit")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
findPreference("pictureInPicture").setEnabled(true);
} else {
findPreference("pictureInPicture").setEnabled(false);
findPreference("pictureInPicture").setSummary("Your device does not support this feature");
}
} }
} }

View file

@ -14,7 +14,6 @@ import androidx.annotation.Nullable;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.base.BasePreferenceFragment; import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
import com.panda3ds.pandroid.app.base.BaseSheetDialog; import com.panda3ds.pandroid.app.base.BaseSheetDialog;
@ -29,7 +28,7 @@ public class GamesFoldersPreferences extends BasePreferenceFragment implements A
@Override @Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
setPreferencesFromResource(R.xml.empty_preferences, rootKey); setPreferencesFromResource(R.xml.empty_preferences, rootKey);
setActivityTitle(R.string.pref_games_folders); setActivityTitle(R.string.pref_game_folders);
refreshList(); refreshList();
pickFolderRequest = registerForActivityResult(openFolderContract, this); pickFolderRequest = registerForActivityResult(openFolderContract, this);
} }

View file

@ -3,19 +3,33 @@ package com.panda3ds.pandroid.app.preferences;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.SwitchPreferenceCompat;
import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.PreferenceActivity; import com.panda3ds.pandroid.app.PreferenceActivity;
import com.panda3ds.pandroid.app.base.BasePreferenceFragment; import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
import com.panda3ds.pandroid.app.preferences.ds.DsListPreferences; import com.panda3ds.pandroid.app.preferences.screen_editor.ScreenLayoutsPreference;
import com.panda3ds.pandroid.data.config.GlobalConfig;
public class GeneralPreferences extends BasePreferenceFragment { public class GeneralPreferences extends BasePreferenceFragment {
@Override @Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
setPreferencesFromResource(R.xml.general_preference, rootKey); setPreferencesFromResource(R.xml.general_preference, rootKey);
setItemClick("appearance.theme", (pref) -> new ThemeSelectorDialog(requireActivity()).show()); setItemClick("appearance.theme", (pref) -> new ThemeSelectorDialog(requireActivity()).show());
setItemClick("appearance.ds", (pref) -> PreferenceActivity.launch(requireActivity(), DsListPreferences.class)); setItemClick("appearance.ds", (pref) -> PreferenceActivity.launch(requireActivity(), ScreenLayoutsPreference.class));
setItemClick("games.folders", (pref) -> PreferenceActivity.launch(requireActivity(), GamesFoldersPreferences.class)); setItemClick("games.folders", (pref) -> PreferenceActivity.launch(requireActivity(), GamesFoldersPreferences.class));
setItemClick("behavior.pictureInPicture", (pref)-> GlobalConfig.set(GlobalConfig.KEY_PICTURE_IN_PICTURE, ((SwitchPreferenceCompat)pref).isChecked()));
setActivityTitle(R.string.general); setActivityTitle(R.string.general);
refresh();
}
@Override
public void onResume() {
super.onResume();
refresh();
}
private void refresh(){
setSwitchValue("behavior.pictureInPicture", GlobalConfig.get(GlobalConfig.KEY_PICTURE_IN_PICTURE));
} }
} }

View file

@ -1,4 +1,4 @@
package com.panda3ds.pandroid.app.preferences.ds; package com.panda3ds.pandroid.app.preferences.screen_editor;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -16,9 +16,8 @@ import com.panda3ds.pandroid.app.BaseActivity;
import com.panda3ds.pandroid.view.ds.DsEditorView; import com.panda3ds.pandroid.view.ds.DsEditorView;
import com.panda3ds.pandroid.view.ds.DsLayoutManager; import com.panda3ds.pandroid.view.ds.DsLayoutManager;
public class DsEditorPreferences extends Fragment { public class ScreenEditorPreference extends Fragment {
private LinearLayout layout; private LinearLayout layout;
private DsEditorView editor;
@Nullable @Nullable
@Override @Override
@ -33,7 +32,7 @@ public class DsEditorPreferences extends Fragment {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
int index = getArguments().getInt("index"); int index = getArguments().getInt("index");
layout.removeAllViews(); layout.removeAllViews();
layout.addView(editor = new DsEditorView(view.getContext(), index), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); layout.addView(new DsEditorView(view.getContext(), index), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
((BaseActivity)requireActivity()).getSupportActionBar().hide(); ((BaseActivity)requireActivity()).getSupportActionBar().hide();
} }

View file

@ -1,4 +1,4 @@
package com.panda3ds.pandroid.app.preferences.ds; package com.panda3ds.pandroid.app.preferences.screen_editor;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -12,7 +12,7 @@ import com.panda3ds.pandroid.app.PreferenceActivity;
import com.panda3ds.pandroid.app.base.BasePreferenceFragment; import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
import com.panda3ds.pandroid.view.ds.DsLayoutManager; import com.panda3ds.pandroid.view.ds.DsLayoutManager;
public class DsListPreferences extends BasePreferenceFragment { public class ScreenLayoutsPreference extends BasePreferenceFragment {
@Override @Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
setPreferencesFromResource(R.xml.empty_preferences, rootKey); setPreferencesFromResource(R.xml.empty_preferences, rootKey);
@ -33,7 +33,7 @@ public class DsListPreferences extends BasePreferenceFragment {
final int index = i; final int index = i;
pref.setOnPreferenceClickListener(preference -> { pref.setOnPreferenceClickListener(preference -> {
PreferenceActivity.launch(requireContext(), DsEditorPreferences.class, new Intent().putExtra("index", index)); PreferenceActivity.launch(requireContext(), ScreenEditorPreference.class, new Intent().putExtra("index", index));
return false; return false;
}); });
screen.addPreference(pref); screen.addPreference(pref);

View file

@ -13,9 +13,12 @@ import androidx.annotation.Nullable;
import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.PandroidApplication; import com.panda3ds.pandroid.app.PandroidApplication;
import com.panda3ds.pandroid.utils.FileUtils;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Objects;
public class AppDataDocumentProvider extends DocumentsProvider { public class AppDataDocumentProvider extends DocumentsProvider {
private static final String ROOT_ID = "root"; private static final String ROOT_ID = "root";
@ -89,8 +92,10 @@ public class AppDataDocumentProvider extends DocumentsProvider {
private void includeFile(MatrixCursor cursor, File file) { private void includeFile(MatrixCursor cursor, File file) {
int flags = 0; int flags = 0;
if (file.isFile()) { if (file.isDirectory()) {
flags = Document.FLAG_SUPPORTS_WRITE; flags = Document.FLAG_DIR_SUPPORTS_CREATE;
} else {
flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_REMOVE | Document.FLAG_SUPPORTS_DELETE;
} }
cursor.newRow() cursor.newRow()
.add(Document.COLUMN_DOCUMENT_ID, obtainDocumentId(file)) .add(Document.COLUMN_DOCUMENT_ID, obtainDocumentId(file))
@ -116,6 +121,40 @@ public class AppDataDocumentProvider extends DocumentsProvider {
return cursor; return cursor;
} }
@Override
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
File parent = obtainFile(parentDocumentId);
File file = new File(parent, displayName);
if (!parent.exists()){
throw new FileNotFoundException("Parent don't exists");
}
if (Objects.equals(mimeType, Document.MIME_TYPE_DIR)){
if (!file.mkdirs()){
throw new FileNotFoundException("Error on create directory");
}
} else {
try {
if (!file.createNewFile()){
throw new Exception("Error on create file");
}
} catch (Exception e){
throw new FileNotFoundException(e.getMessage());
}
}
return obtainDocumentId(file);
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
File file = obtainFile(documentId);
if (file.exists()){
FileUtils.delete(file.getAbsolutePath());
} else {
throw new FileNotFoundException("File not exists");
}
}
@Override @Override
public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException { public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
return ParcelFileDescriptor.open(obtainFile(documentId), ParcelFileDescriptor.parseMode(mode)); return ParcelFileDescriptor.open(obtainFile(documentId), ParcelFileDescriptor.parseMode(mode));

View file

@ -93,7 +93,7 @@ public class SMDH {
smdh.position(META_OFFSET + (512 * i) + 0x80); smdh.position(META_OFFSET + (512 * i) + 0x80);
data = new byte[0x100]; data = new byte[0x100];
smdh.get(data); smdh.get(data);
title[i] = convertString(data).replaceAll("\n", " "); title[i] = convertString(data);
smdh.position(META_OFFSET + (512 * i) + 0x180); smdh.position(META_OFFSET + (512 * i) + 0x180);
data = new byte[0x80]; data = new byte[0x80];

View file

@ -22,11 +22,12 @@ public class GlobalConfig {
public static DataModel data; public static DataModel data;
public static final Key<Boolean> KEY_SHADER_JIT = new Key<>("emu.shader_jit", false); public static final Key<Boolean> KEY_SHADER_JIT = new Key<>("emu.shader_jit", false);
public static final Key<Boolean> KEY_PICTURE_IN_PICTURE = new Key<>("app.behavior.pictureInPicture", false);
public static final Key<Boolean> KEY_SHOW_PERFORMANCE_OVERLAY = new Key<>("dev.performanceOverlay", false); public static final Key<Boolean> KEY_SHOW_PERFORMANCE_OVERLAY = new Key<>("dev.performanceOverlay", false);
public static final Key<Boolean> KEY_PICTURE_IN_PICTURE = new Key<>("dev.pictureInPicture", false);
public static final Key<Boolean> KEY_LOGGER_SERVICE = new Key<>("dev.loggerService", false); public static final Key<Boolean> KEY_LOGGER_SERVICE = new Key<>("dev.loggerService", false);
public static final Key<Integer> KEY_APP_THEME = new Key<>("app.theme", THEME_ANDROID); public static final Key<Integer> KEY_APP_THEME = new Key<>("app.theme", THEME_ANDROID);
public static final Key<Boolean> KEY_SCREEN_GAMEPAD_VISIBLE = new Key<>("app.screen_gamepad.visible", true); public static final Key<Boolean> KEY_SCREEN_GAMEPAD_VISIBLE = new Key<>("app.screen_gamepad.visible", true);
public static final Key<Integer> KEY_CURRENT_DS_LAYOUT = new Key<>("app.ds.current_layout",0);
public static final Key<String> KEY_DS_LAYOUTS = new Key<>("app.ds.layouts", ""); public static final Key<String> KEY_DS_LAYOUTS = new Key<>("app.ds.layouts", "");
public static void initialize() { public static void initialize() {

View file

@ -1,6 +1,7 @@
package com.panda3ds.pandroid.utils; package com.panda3ds.pandroid.utils;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.res.TypedArray; import android.content.res.TypedArray;

View file

@ -157,6 +157,15 @@ public class GameUtils {
writeChanges(); writeChanges();
} }
public static GameMetadata findGameById(String id) {
for (GameMetadata game: getGames()){
if (game.getId().equals(id)){
return game;
}
}
return null;
}
private static class DataModel { private static class DataModel {
public final List<GameMetadata> games = new ArrayList<>(); public final List<GameMetadata> games = new ArrayList<>();
public final HashMap<String, GamesFolder> folders = new HashMap<>(); public final HashMap<String, GamesFolder> folders = new HashMap<>();

View file

@ -16,6 +16,7 @@ import com.panda3ds.pandroid.app.base.BottomAlertDialog;
import com.panda3ds.pandroid.data.SMDH; import com.panda3ds.pandroid.data.SMDH;
import com.panda3ds.pandroid.data.config.GlobalConfig; import com.panda3ds.pandroid.data.config.GlobalConfig;
import com.panda3ds.pandroid.data.game.GameMetadata; import com.panda3ds.pandroid.data.game.GameMetadata;
import com.panda3ds.pandroid.utils.CompatUtils;
import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.utils.Constants;
import com.panda3ds.pandroid.utils.GameUtils; import com.panda3ds.pandroid.utils.GameUtils;
import com.panda3ds.pandroid.utils.PerformanceMonitor; import com.panda3ds.pandroid.utils.PerformanceMonitor;
@ -103,7 +104,7 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
.setMessage(R.string.dialog_message_invalid_rom) .setMessage(R.string.dialog_message_invalid_rom)
.setPositiveButton(android.R.string.ok, (dialog, witch) -> { .setPositiveButton(android.R.string.ok, (dialog, witch) -> {
dialog.dismiss(); dialog.dismiss();
((Activity) context).finish(); CompatUtils.findActivity(context).finishAndRemoveTask();
}) })
.setCancelable(false) .setCancelable(false)
.show(); .show();

View file

@ -30,10 +30,8 @@ import com.panda3ds.pandroid.math.Vector2;
import com.panda3ds.pandroid.utils.CompatUtils; import com.panda3ds.pandroid.utils.CompatUtils;
import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.utils.Constants;
@SuppressLint("ViewConstructor")
public class DsEditorView extends FrameLayout { public class DsEditorView extends FrameLayout {
private final int COLOR_TOP_SELECTION;
private final int COLOR_BOTTOM_SELECTION;
private final float SIZE_DP; private final float SIZE_DP;
private final Paint selectionPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint selectionPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@ -54,10 +52,10 @@ public class DsEditorView extends FrameLayout {
super(context); super(context);
layout = (DsLayout) DsLayoutManager.createLayout(index); layout = (DsLayout) DsLayoutManager.createLayout(index);
SIZE_DP = CompatUtils.applyDimen(TypedValue.COMPLEX_UNIT_DIP, 1); SIZE_DP = CompatUtils.applyDimen(TypedValue.COMPLEX_UNIT_DIP, 1);
COLOR_BOTTOM_SELECTION = CompatUtils.resolveColor(context, androidx.appcompat.R.attr.colorPrimary); int colorBottomSelection = CompatUtils.resolveColor(context, androidx.appcompat.R.attr.colorPrimary);
COLOR_TOP_SELECTION = CompatUtils.resolveColor(context, com.google.android.material.R.attr.colorAccent); int colorTopSelection = CompatUtils.resolveColor(context, com.google.android.material.R.attr.colorAccent);
selectionPaint.setColor(COLOR_TOP_SELECTION); selectionPaint.setColor(colorTopSelection);
selectionPaint.setStrokeWidth(SIZE_DP * 2); selectionPaint.setStrokeWidth(SIZE_DP * 2);
selectionPaint.setPathEffect(new DashPathEffect(new float[]{SIZE_DP * 10, SIZE_DP * 10}, 0.0f)); selectionPaint.setPathEffect(new DashPathEffect(new float[]{SIZE_DP * 10, SIZE_DP * 10}, 0.0f));
selectionPaint.setStyle(Paint.Style.STROKE); selectionPaint.setStyle(Paint.Style.STROKE);
@ -100,9 +98,7 @@ public class DsEditorView extends FrameLayout {
} }
@Override @Override
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(AdapterView<?> parent) {}
}
}); });
} }
@ -113,7 +109,7 @@ public class DsEditorView extends FrameLayout {
}); });
spacePoint = new PointView(); spacePoint = new PointView();
spacePoint.setColor(Color.WHITE, COLOR_TOP_SELECTION); spacePoint.setColor(CompatUtils.resolveColor(context, com.google.android.material.R.attr.colorOnPrimary), colorTopSelection);
spacePoint.setOnTouchListener((view, motion) -> { spacePoint.setOnTouchListener((view, motion) -> {
layout.getCurrentModel().space = (motion.getX() + spacePoint.x()) / (float) width; layout.getCurrentModel().space = (motion.getX() + spacePoint.x()) / (float) width;
refreshPoints(); refreshPoints();
@ -124,7 +120,7 @@ public class DsEditorView extends FrameLayout {
setOnClickListener(v -> { setOnClickListener(v -> {
if (layout.getCurrentModel().mode == Mode.SINGLE) { if (layout.getCurrentModel().mode == Mode.SINGLE) {
layout.getCurrentModel().singleTop = !layout.getCurrentModel().singleTop; layout.getCurrentModel().onlyTop = !layout.getCurrentModel().onlyTop;
refreshPoints(); refreshPoints();
} }
}); });
@ -132,40 +128,38 @@ public class DsEditorView extends FrameLayout {
topDisplay = new PointView(); topDisplay = new PointView();
topDisplay.setText(R.string.top_display); topDisplay.setText(R.string.top_display);
topDisplay.setOnTouchListener(new DisplayTouchEvent(true)); topDisplay.setOnTouchListener(new DisplayTouchEvent(true));
topDisplay.setTextColor(COLOR_TOP_SELECTION); topDisplay.setTextColor(colorTopSelection);
topDisplay.setBackground(new SelectionDrawable(COLOR_TOP_SELECTION)); topDisplay.setBackground(new SelectionDrawable(colorTopSelection));
bottomDisplay = new PointView(); bottomDisplay = new PointView();
bottomDisplay.setText(R.string.bottom_display); bottomDisplay.setText(R.string.bottom_display);
bottomDisplay.setOnTouchListener(new DisplayTouchEvent(false)); bottomDisplay.setOnTouchListener(new DisplayTouchEvent(false));
bottomDisplay.setTextColor(COLOR_BOTTOM_SELECTION); bottomDisplay.setTextColor(colorBottomSelection);
bottomDisplay.setBackground(new SelectionDrawable(COLOR_BOTTOM_SELECTION)); bottomDisplay.setBackground(new SelectionDrawable(colorBottomSelection));
topDisplayResizer = new PointView(); topDisplayResizer = new PointView();
topDisplayResizer.setColor(0, COLOR_TOP_SELECTION); topDisplayResizer.setColor(0, colorTopSelection);
topDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(true)); topDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(true));
bottomDisplayResizer = new PointView(); bottomDisplayResizer = new PointView();
bottomDisplayResizer.setColor(0, COLOR_BOTTOM_SELECTION); bottomDisplayResizer.setColor(0, colorBottomSelection);
bottomDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(false)); bottomDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(false));
} }
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { public void draw(Canvas canvas) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.draw(canvas);
int width = getMeasuredWidth(); if (this.width != getWidth() || this.height != getHeight()) {
int height = getMeasuredHeight(); this.width = getWidth();
if (this.width != width || this.height != height) { this.height = getHeight();
this.width = width;
this.height = height;
refreshLayout(); refreshLayout();
} }
} }
private void refreshPoints() { private void refreshPoints() {
Model data = layout.getCurrentModel(); Model data = layout.getCurrentModel();
data.preferredTop.fixOverlay(width, height, (int) (SIZE_DP*5)); data.preferredTop.fixOverlay(width, height, (int) (SIZE_DP * 5));
data.preferredBottom.fixOverlay(width, height, (int) (SIZE_DP*30)); data.preferredBottom.fixOverlay(width, height, (int) (SIZE_DP * 30));
layout.update(width, height); layout.update(width, height);
Rect bottomDisplay = layout.getBottomDisplayBounds(); Rect bottomDisplay = layout.getBottomDisplayBounds();
Rect topDisplay = layout.getTopDisplayBounds(); Rect topDisplay = layout.getTopDisplayBounds();
@ -322,6 +316,7 @@ public class DsEditorView extends FrameLayout {
private class DisplayResizeTouchEvent implements OnTouchListener { private class DisplayResizeTouchEvent implements OnTouchListener {
private final boolean topScreen; private final boolean topScreen;
private DisplayResizeTouchEvent(boolean topScreen) { private DisplayResizeTouchEvent(boolean topScreen) {
this.topScreen = topScreen; this.topScreen = topScreen;
} }

View file

@ -10,17 +10,17 @@ class DsLayout implements ConsoleLayout {
private final Rect topDisplay = new Rect(); private final Rect topDisplay = new Rect();
private final Rect bottomDisplay = new Rect(); private final Rect bottomDisplay = new Rect();
private final Vector2 screenSize = new Vector2(0,0); private final Vector2 screenSize = new Vector2(0, 0);
private final Vector2 sourceTop = new Vector2(0,0); private final Vector2 sourceTop = new Vector2(0, 0);
private final Vector2 sourceBottom = new Vector2(0,0); private final Vector2 sourceBottom = new Vector2(0, 0);
private final Model[] modes = new Model[2]; private final Model[] modes = new Model[2];
public DsLayout(Model landscape, Model portrait){ public DsLayout(Model landscape, Model portrait) {
modes[0] = landscape; modes[0] = landscape;
modes[1] = portrait; modes[1] = portrait;
} }
public DsLayout(){ public DsLayout() {
this(new Model(), new Model()); this(new Model(), new Model());
} }
@ -52,10 +52,10 @@ class DsLayout implements ConsoleLayout {
return topDisplay; return topDisplay;
} }
public void update(){ public void update() {
Model data = getCurrentModel(); Model data = getCurrentModel();
Mode mode = data.mode; Mode mode = data.mode;
switch (mode){ switch (mode) {
case RELATIVE: case RELATIVE:
relative(data); relative(data);
break; break;
@ -69,22 +69,18 @@ class DsLayout implements ConsoleLayout {
} }
private void absolute(Model data) { private void absolute(Model data) {
if (data.lockAspect){ if (data.lockAspect) {
data.preferredTop.applyWithAspect(topDisplay, (int) screenSize.x, (double) sourceTop.y/sourceTop.x); data.preferredTop.applyWithAspect(topDisplay, (int) screenSize.x, (double) sourceTop.y / sourceTop.x);
data.preferredBottom.applyWithAspect(bottomDisplay, (int) screenSize.x, (double) sourceBottom.y/sourceBottom.x); data.preferredBottom.applyWithAspect(bottomDisplay, (int) screenSize.x, (double) sourceBottom.y / sourceBottom.x);
} else { } else {
data.preferredTop.apply(topDisplay, (int) screenSize.x, (int) screenSize.y); data.preferredTop.apply(topDisplay, (int) screenSize.x, (int) screenSize.y);
data.preferredBottom.apply(bottomDisplay, (int) screenSize.x, (int) screenSize.y); data.preferredBottom.apply(bottomDisplay, (int) screenSize.x, (int) screenSize.y);
} }
} }
/**
* SINGLE LAYOUT:
* SHOW ONLY SCREEN IN FIT MODE
*/
private void single(Model data) { private void single(Model data) {
Vector2 source = data.singleTop ? sourceTop : sourceBottom; Vector2 source = data.onlyTop ? sourceTop : sourceBottom;
Rect dest = data.singleTop ? topDisplay : bottomDisplay; Rect dest = data.onlyTop ? topDisplay : bottomDisplay;
if (data.lockAspect) { if (data.lockAspect) {
int x = 0, y = 0; int x = 0, y = 0;
@ -99,11 +95,11 @@ class DsLayout implements ConsoleLayout {
height = (int) screenSize.y; height = (int) screenSize.y;
x = (int) ((screenSize.x - width) / 2); x = (int) ((screenSize.x - width) / 2);
} }
dest.set(x,y,x+width,y+height); dest.set(x, y, x + width, y + height);
} else { } else {
dest.set(0,0, (int) screenSize.x, (int) screenSize.y); dest.set(0, 0, (int) screenSize.x, (int) screenSize.y);
} }
(data.singleTop ? bottomDisplay : topDisplay).set(0,0,0,0); (data.onlyTop ? bottomDisplay : topDisplay).set(0, 0, 0, 0);
} }
@ -122,7 +118,7 @@ class DsLayout implements ConsoleLayout {
Rect topDisplay = this.topDisplay; Rect topDisplay = this.topDisplay;
Rect bottomDisplay = this.bottomDisplay; Rect bottomDisplay = this.bottomDisplay;
if (data.reverse){ if (data.reverse) {
topSourceSize = this.sourceBottom; topSourceSize = this.sourceBottom;
bottomSourceSize = this.sourceTop; bottomSourceSize = this.sourceTop;
@ -144,15 +140,17 @@ class DsLayout implements ConsoleLayout {
topDisplay.set(0, 0, topDisplayWidth, topDisplayHeight); topDisplay.set(0, 0, topDisplayWidth, topDisplayHeight);
bottomDisplay.set(topDisplayWidth, 0, topDisplayWidth + (screenWidth - topDisplayWidth), bottomDisplayHeight); bottomDisplay.set(topDisplayWidth, 0, topDisplayWidth + (screenWidth - topDisplayWidth), bottomDisplayHeight);
switch (data.gravity){ switch (data.gravity) {
case Gravity.CENTER:{ case Gravity.CENTER: {
bottomDisplay.offset(0, (screenHeight-bottomDisplay.height())/2); bottomDisplay.offset(0, (screenHeight - bottomDisplay.height()) / 2);
topDisplay.offset(0, (screenHeight-topDisplay.height())/2); topDisplay.offset(0, (screenHeight - topDisplay.height()) / 2);
}break; }
case Gravity.BOTTOM:{ break;
bottomDisplay.offset(0, (screenHeight-bottomDisplay.height())); case Gravity.BOTTOM: {
topDisplay.offset(0, (screenHeight-topDisplay.height())); bottomDisplay.offset(0, (screenHeight - bottomDisplay.height()));
}break; topDisplay.offset(0, (screenHeight - topDisplay.height()));
}
break;
} }
} else { } else {

View file

@ -20,11 +20,11 @@ public class DsLayoutManager {
Model model2 = new Model(); Model model2 = new Model();
model2.mode = Mode.SINGLE; model2.mode = Mode.SINGLE;
model2.singleTop = false; model2.onlyTop = false;
Model model3 = new Model(); Model model3 = new Model();
model3.mode = Mode.SINGLE; model3.mode = Mode.SINGLE;
model3.singleTop = true; model3.onlyTop = true;
data.models.add(new Model[]{model1, model1.clone()}); data.models.add(new Model[]{model1, model1.clone()});
data.models.add(new Model[]{model2, model2.clone()}); data.models.add(new Model[]{model2, model2.clone()});

View file

@ -12,7 +12,7 @@ class Model implements Cloneable {
public final Bounds preferredTop = new Bounds(); public final Bounds preferredTop = new Bounds();
public final Bounds preferredBottom = new Bounds(); public final Bounds preferredBottom = new Bounds();
public boolean reverse = false; public boolean reverse = false;
public boolean singleTop = true; public boolean onlyTop = true;
public float space = 0.6f; public float space = 0.6f;
public int gravity = Gravity.CENTER; public int gravity = Gravity.CENTER;
public boolean lockAspect = true; public boolean lockAspect = true;

View file

@ -4,6 +4,6 @@
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#FF000000" android:fillColor="#FFF"
android:pathData="M8,19h3v4h2v-4h3l-4,-4 -4,4zM16,5h-3L13,1h-2v4L8,5l4,4 4,-4zM4,11v2h16v-2L4,11z"/> android:pathData="M8,19h3v4h2v-4h3l-4,-4 -4,4zM16,5h-3L13,1h-2v4L8,5l4,4 4,-4zM4,11v2h16v-2L4,11z"/>
</vector> </vector>

View file

@ -2,8 +2,9 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
android:tint="?colorOnSurface">
<path <path
android:fillColor="#FF000000" android:fillColor="#FFF"
android:pathData="M7.41,8.59 L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/> android:pathData="M7.41,8.59 L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
</vector> </vector>

View file

@ -2,8 +2,9 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
android:tint="?colorOnSurface">
<path <path
android:fillColor="#FF000000" android:fillColor="#FFF"
android:pathData="M7.41,15.41 L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/> android:pathData="M7.41,15.41 L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
</vector> </vector>

View file

@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#000000" <vector android:height="24dp" android:tint="?colorOnSurface"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/> <path android:fillColor="@android:color/white" android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/>

View file

@ -2,8 +2,9 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
android:tint="?colorOnSurface">
<path <path
android:fillColor="#FF000000" android:fillColor="#FFF"
android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3zM14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4z"/> android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3zM14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4z"/>
</vector> </vector>

View file

@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#000000" <vector android:height="24dp" android:tint="?colorOnSurface"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/> <path android:fillColor="@android:color/white" android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>

View file

@ -1,5 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp" <vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24" android:tint="?colorOnSurface" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/> <path android:fillColor="@android:color/white" android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
</vector> </vector>

View file

@ -2,8 +2,9 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
android:tint="?colorOnSurface">
<path <path
android:fillColor="?colorOnSurface" android:fillColor="#FFF"
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/> android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
</vector> </vector>

View file

@ -1,5 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp" <vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24" android:tint="?colorOnSurface" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M21,11l-6,-6v5H8c-2.76,0 -5,2.24 -5,5v4h2v-4c0,-1.65 1.35,-3 3,-3h7v5L21,11z"/> <path android:fillColor="@android:color/white" android:pathData="M21,11l-6,-6v5H8c-2.76,0 -5,2.24 -5,5v4h2v-4c0,-1.65 1.35,-3 3,-3h7v5L21,11z"/>
</vector> </vector>

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<layer-list>
<item>
<shape>
<padding android:bottom="3dp" android:top="3dp" android:right="3dp" android:left="3dp"/>
<solid android:color="#0000"/>
</shape>
</item>
<item>
<shape>
<size android:width="18dp" android:height="18dp"/>
<corners android:radius="999dp"/>
<solid android:color="?colorPrimary"/>
</shape>
</item>
</layer-list>
</item>
<item>
<layer-list>
<item>
<shape>
<padding android:bottom="3dp" android:top="3dp" android:right="3dp" android:left="3dp"/>
<solid android:color="#0000"/>
</shape>
</item>
<item>
<shape android:tintMode="multiply" android:tint="#2FFF">
<size android:width="18dp" android:height="18dp"/>
<corners android:radius="999dp"/>
<solid android:color="?colorOnSurface"/>
</shape>
</item>
</layer-list>
</item>
</selector>

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<shape android:tintMode="multiply" android:tint="#5FFF">
<padding android:left="5dp" android:right="5dp" />
<solid android:color="?colorPrimary"/>
<corners android:radius="24dp"/>
<size android:width="32dp" android:height="22dp"/>
</shape>
</item>
<item>
<shape android:tintMode="multiply" android:tint="#2FFF">
<padding android:left="5dp" android:right="5dp" />
<solid android:color="?colorOnSurface"/>
<corners android:radius="24dp"/>
<size android:width="32dp" android:height="22dp"/>
</shape>
</item>
</selector>

View file

@ -92,15 +92,26 @@
android:gravity="end|center"> android:gravity="end|center">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/remove" android:id="@+id/shortcut"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/remove" app:icon="@drawable/ic_shortcut"
app:icon="@drawable/ic_delete"
app:iconTint="?colorOnSurfaceVariant" app:iconTint="?colorOnSurfaceVariant"
android:textColor="?colorOnSurfaceVariant" android:textColor="?colorOnSurfaceVariant"
android:backgroundTint="?colorSurfaceVariant" android:backgroundTint="?colorSurfaceVariant"
android:layout_marginHorizontal="10dp"/> android:layout_marginHorizontal="10dp"
style="@style/Widget.Material3.Button.IconButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_delete"
app:iconTint="?colorSurfaceVariant"
android:textColor="?colorOnSurfaceVariant"
android:backgroundTint="?colorOnSurfaceVariant"
android:layout_marginHorizontal="10dp"
style="@style/Widget.Material3.Button.IconButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/play" android:id="@+id/play"
@ -111,6 +122,7 @@
android:textColor="?colorOnPrimary" android:textColor="?colorOnPrimary"
android:backgroundTint="?colorPrimary" android:backgroundTint="?colorPrimary"
app:icon="@drawable/ic_play" app:icon="@drawable/ic_play"
app:rippleColor="?colorOnPrimary"
app:iconTint="?colorOnPrimary"/> app:iconTint="?colorOnPrimary"/>
</LinearLayout> </LinearLayout>

View file

@ -31,6 +31,7 @@
android:textColor="?colorOnSurface" android:textColor="?colorOnSurface"
android:text="@string/app_name" android:text="@string/app_name"
android:textStyle="bold" android:textStyle="bold"
android:textSize="14sp"
android:gravity="center" android:gravity="center"
android:layout_marginTop="10dp"/> android:layout_marginTop="10dp"/>

View file

@ -53,6 +53,8 @@
<string name="pref_logger_service_summary">Grave os registros para um arquivo.</string> <string name="pref_logger_service_summary">Grave os registros para um arquivo.</string>
<string name="pref_shader_jit_title">Shader Jit</string> <string name="pref_shader_jit_title">Shader Jit</string>
<string name="pref_shader_jit_summary">Usar recompilador de shaders.</string> <string name="pref_shader_jit_summary">Usar recompilador de shaders.</string>
<string name="pref_picture_in_picture_title">Picture In Picture</string>
<string name="pref_picture_in_picture_summary">Entrar em modo picture in picture quando a janela sai de foco</string>
<string name="graphics">Gráficos</string> <string name="graphics">Gráficos</string>
<string name="loading">Carregando</string> <string name="loading">Carregando</string>
<string name="rotate">Rotacionar</string> <string name="rotate">Rotacionar</string>
@ -67,12 +69,12 @@
<string name="bottom_display">Tela inferior</string> <string name="bottom_display">Tela inferior</string>
<string name="top_display">Tela superior</string> <string name="top_display">Tela superior</string>
<string name="fix_aspect">Manter porporção</string> <string name="fix_aspect">Manter porporção</string>
<string name="dual_screen_layouts">Disposições para as duas telas.</string> <string name="dual_screen_layouts">Disposição das telas</string>
<string name="dual_screen_layouts_summary">Altere as disposições disponiveis para as telas do console</string> <string name="dual_screen_layouts_summary">Altere as disposições disponiveis para as telas do console</string>
<string name="click_to_change">Clique para mudar</string> <string name="click_to_change">Clique para mudar</string>
<string name="swap_screen">Mudar telas</string> <string name="swap_screen">Mudar telas</string>
<string name="pref_games_folders_summary">Pastas usadas para importar os jogos</string> <string name="pref_game_folders_summary">Pastas usadas para importar os jogos</string>
<string name="pref_games_folders">Pastas de jogos</string> <string name="pref_game_folders">Pastas de jogos</string>
<string name="import_folder">Adicionar pasta</string> <string name="import_folder">Adicionar pasta</string>
<string name="games_count_f">%d Jogos</string> <string name="games_count_f">%d Jogos</string>
<string name="directory">Diretorio</string> <string name="directory">Diretorio</string>
@ -85,4 +87,7 @@
<string name="region_australia">Australia</string> <string name="region_australia">Australia</string>
<string name="region_korean">Coréia</string> <string name="region_korean">Coréia</string>
<string name="region_taiwan">Taiwan</string> <string name="region_taiwan">Taiwan</string>
<string name="behavior">Comportamento</string>
<string name="invalid_game">Jogo invalido</string>
<string name="tools">Ferramentas</string>
</resources> </resources>

View file

@ -52,7 +52,6 @@
<!-- Advanced Settings --> <!-- Advanced Settings -->
<string name="advanced_options">Advanced options</string> <string name="advanced_options">Advanced options</string>
<string name="pref_advanced_summary">Logger, performance statistics, etc.</string> <string name="pref_advanced_summary">Logger, performance statistics, etc.</string>
<string name="display">Display</string>
<string name="pref_performance_monitor_title">Performance monitor</string> <string name="pref_performance_monitor_title">Performance monitor</string>
<string name="pref_performance_monitor_summary">Show overlay with fps, memory, etc.</string> <string name="pref_performance_monitor_summary">Show overlay with fps, memory, etc.</string>
<string name="pref_picture_in_picture_title">Picture In Picture</string> <string name="pref_picture_in_picture_title">Picture In Picture</string>
@ -60,7 +59,7 @@
<string name="graphics">Graphics</string> <string name="graphics">Graphics</string>
<string name="pref_shader_jit_title">Shader JIT</string> <string name="pref_shader_jit_title">Shader JIT</string>
<string name="pref_shader_jit_summary">Use shader recompiler.</string> <string name="pref_shader_jit_summary">Use shader recompiler.</string>
<string name="debug">Debug</string> <string name="tools">Tools</string>
<string name="pref_logger_service_title">Logger</string> <string name="pref_logger_service_title">Logger</string>
<string name="pref_logger_service_summary">Store application logs to file.</string> <string name="pref_logger_service_summary">Store application logs to file.</string>
<string name="loading">Loading</string> <string name="loading">Loading</string>
@ -71,21 +70,23 @@
<string name="dialog_message_invalid_rom">Make sure it\'s a valid 3DS ROM and that storage permissions are configured properly.</string> <string name="dialog_message_invalid_rom">Make sure it\'s a valid 3DS ROM and that storage permissions are configured properly.</string>
<string name="system">System</string> <string name="system">System</string>
<string name="general">General</string> <string name="general">General</string>
<string name="pref_general_summary">General application configurations.</string> <string name="pref_general_summary">General application configuration.</string>
<string name="bottom_display">Bottom Display</string> <string name="dual_screen_layouts">Screen layouts</string>
<string name="top_display">Top Display</string>
<string name="fix_aspect">Fix aspect</string>
<string name="dual_screen_layouts">Dual Screen layouts</string>
<string name="dual_screen_layouts_summary">Change layout of console screens.</string> <string name="dual_screen_layouts_summary">Change layout of console screens.</string>
<string name="click_to_change">Click to change</string> <string name="click_to_change">Click to change</string>
<string name="swap_screen">Swap screen</string> <string name="swap_screen">Swap screen</string>
<string name="pref_games_folders_summary">Folders for auto import games</string> <string name="pref_game_folders_summary">Folders for importing games</string>
<string name="pref_games_folders">Games folders</string> <string name="pref_game_folders">Game folders</string>
<string name="import_folder">Import folder</string> <string name="import_folder">Import folder</string>
<string name="games_count_f">%d Games</string> <string name="games_count_f">%d Games</string>
<string name="directory">Directory</string> <string name="directory">Directory</string>
<string name="remove">Remove</string> <string name="remove">Remove</string>
<string name="play">Play</string> <string name="play">Play</string>
<!-- Screen layout editor -->
<string name="fix_aspect">Maintain aspect ratio</string>
<string name="bottom_display">Bottom Display</string>
<string name="top_display">Top Display</string>
<!-- Game about -->
<string name="region">Region</string> <string name="region">Region</string>
<string name="region_north_armerican">North American</string> <string name="region_north_armerican">North American</string>
<string name="region_japan">Japan</string> <string name="region_japan">Japan</string>
@ -93,4 +94,6 @@
<string name="region_australia">Australia</string> <string name="region_australia">Australia</string>
<string name="region_korean">Korean</string> <string name="region_korean">Korean</string>
<string name="region_taiwan">Taiwan</string> <string name="region_taiwan">Taiwan</string>
<string name="behavior">Behavior</string>
<string name="invalid_game">Invalid game</string>
</resources> </resources>

View file

@ -3,9 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory <PreferenceCategory
app:iconSpaceReserved="false" app:title="@string/tools"
app:title="@string/display"> app:iconSpaceReserved="false">
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="performanceMonitor" android:key="performanceMonitor"
app:title="@string/pref_performance_monitor_title" app:title="@string/pref_performance_monitor_title"
@ -30,17 +29,4 @@
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/debug">
<SwitchPreferenceCompat
android:key="loggerService"
app:iconSpaceReserved="false"
app:title="@string/pref_logger_service_title"
android:defaultValue="true"
android:summaryOn="@string/pref_logger_service_summary"
android:summaryOff="@string/pref_logger_service_summary"/>
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View file

@ -20,8 +20,17 @@
app:iconSpaceReserved="false"> app:iconSpaceReserved="false">
<Preference <Preference
android:key="games.folders" android:key="games.folders"
app:title="@string/pref_games_folders" app:title="@string/pref_game_folders"
app:summary="@string/pref_games_folders_summary" app:summary="@string/pref_game_folders_summary"
app:iconSpaceReserved="false"/>
</PreferenceCategory>
<PreferenceCategory
app:title="@string/behavior"
app:iconSpaceReserved="false">
<SwitchPreferenceCompat
android:key="behavior.pictureInPicture"
app:title="@string/pref_picture_in_picture_title"
app:summary="@string/pref_picture_in_picture_summary"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>