mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-07 22:55:40 +12:00
Pandroid: Screen layout editor (#425)
* Add Dynamic colors option for Android 12 or above (#8)
* Dynamic color for android 12 or above @Ishan09811
* Revert "Add Dynamic colors option for Android 12 or above (#8)" (#9)
This reverts commit 6ee1a39fb4
.
* Material Switch (#10) | ISHAN | REVIEW
* Review Material Switch
* basic implement, ds editor, and remake theme selector
* add swap screen in drawer
* basic file provider for access private folder from file explorer
* Pandroid: Game about dialog (#7)
* Alert dialog idea
* Reimplement ishan game dialog.
* Add Picture In Picture support with a option (#11)
- Ishan picture in picture pr
* some fixes
* Add game to launcher and some clear code.
---------
Co-authored-by: Ishan09811 <156402647+Ishan09811@users.noreply.github.com>
Co-authored-by: gabriel <gabriel>
This commit is contained in:
parent
90f8fbfcb4
commit
02496b57eb
84 changed files with 2677 additions and 374 deletions
|
@ -35,7 +35,10 @@
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".app.GameActivity"
|
android:name=".app.GameActivity"
|
||||||
android:configChanges="screenSize|screenLayout|orientation|density|uiMode">
|
android:supportsPictureInPicture="true"
|
||||||
|
android:taskAffinity="emulator.GameActivity"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|density|uiMode">
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".app.editor.CodeEditorActivity"
|
android:name=".app.editor.CodeEditorActivity"
|
||||||
|
@ -49,6 +52,26 @@
|
||||||
<activity android:name=".app.preferences.InputMapActivity"
|
<activity android:name=".app.preferences.InputMapActivity"
|
||||||
android:configChanges="density|orientation|screenSize"/>
|
android:configChanges="density|orientation|screenSize"/>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:authorities="com.panda3ds.pandroid.user"
|
||||||
|
android:name="com.panda3ds.pandroid.app.provider.AppDataDocumentProvider"
|
||||||
|
android:exported="true"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||||
|
</intent-filter>
|
||||||
|
</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>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package com.panda3ds.pandroid.app;
|
package com.panda3ds.pandroid.app;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import com.panda3ds.pandroid.R;
|
|
||||||
|
import com.google.android.material.color.DynamicColors;
|
||||||
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,5 +30,8 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
private void applyTheme() {
|
private void applyTheme() {
|
||||||
currentTheme = PandroidApplication.getThemeId();
|
currentTheme = PandroidApplication.getThemeId();
|
||||||
setTheme(currentTheme);
|
setTheme(currentTheme);
|
||||||
|
if (GlobalConfig.get(GlobalConfig.KEY_APP_THEME) == GlobalConfig.THEME_ANDROID){
|
||||||
|
DynamicColors.applyToActivityIfAvailable(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,22 +14,29 @@ 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 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;
|
||||||
import com.panda3ds.pandroid.app.game.DrawerFragment;
|
import com.panda3ds.pandroid.app.game.DrawerFragment;
|
||||||
|
import com.panda3ds.pandroid.app.game.EmulatorCallback;
|
||||||
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
import com.panda3ds.pandroid.input.InputHandler;
|
import com.panda3ds.pandroid.input.InputHandler;
|
||||||
import com.panda3ds.pandroid.input.InputMap;
|
import com.panda3ds.pandroid.input.InputMap;
|
||||||
import com.panda3ds.pandroid.utils.Constants;
|
import com.panda3ds.pandroid.utils.Constants;
|
||||||
import com.panda3ds.pandroid.view.PandaGlSurfaceView;
|
import com.panda3ds.pandroid.view.PandaGlSurfaceView;
|
||||||
import com.panda3ds.pandroid.view.PandaLayoutController;
|
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;
|
import com.panda3ds.pandroid.view.utils.PerformanceView;
|
||||||
|
|
||||||
public class GameActivity extends BaseActivity {
|
public class GameActivity extends BaseActivity implements EmulatorCallback {
|
||||||
private final DrawerFragment drawerFragment = new DrawerFragment();
|
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
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
@ -43,6 +53,8 @@ public class GameActivity extends BaseActivity {
|
||||||
PandaGlSurfaceView pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.ACTIVITY_PARAMETER_PATH));
|
PandaGlSurfaceView pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.ACTIVITY_PARAMETER_PATH));
|
||||||
setContentView(R.layout.game_activity);
|
setContentView(R.layout.game_activity);
|
||||||
|
|
||||||
|
renderer = pandaSurface.getRenderer();
|
||||||
|
|
||||||
((FrameLayout) findViewById(R.id.panda_gl_frame))
|
((FrameLayout) findViewById(R.id.panda_gl_frame))
|
||||||
.addView(pandaSurface, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
.addView(pandaSurface, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
|
||||||
|
@ -50,9 +62,7 @@ public class GameActivity extends BaseActivity {
|
||||||
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));
|
||||||
|
@ -63,6 +73,13 @@ public class GameActivity extends BaseActivity {
|
||||||
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
|
||||||
|
@ -78,13 +95,31 @@ public class GameActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void goToPictureInPicture() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
builder.setAutoEnterEnabled(true);
|
||||||
|
builder.setSeamlessResizeEnabled(true);
|
||||||
|
}
|
||||||
|
builder.setAspectRatio(new Rational(10, 14));
|
||||||
|
enterPictureInPictureMode(builder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
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) {
|
||||||
|
@ -104,6 +139,13 @@ public class GameActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void swapScreens(int index) {
|
||||||
|
currentDsLayout = index;
|
||||||
|
GlobalConfig.set(GlobalConfig.KEY_CURRENT_DS_LAYOUT,index);
|
||||||
|
renderer.setLayout(DsLayoutManager.createLayout(currentDsLayout));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchGenericMotionEvent(MotionEvent ev) {
|
public boolean dispatchGenericMotionEvent(MotionEvent ev) {
|
||||||
if ((!drawerFragment.isOpened()) && InputHandler.processMotionEvent(ev)) {
|
if ((!drawerFragment.isOpened()) && InputHandler.processMotionEvent(ev)) {
|
||||||
|
@ -113,12 +155,24 @@ public class GameActivity extends BaseActivity {
|
||||||
return super.dispatchGenericMotionEvent(ev);
|
return super.dispatchGenericMotionEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureModeChanged(boolean 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
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
if (AlberDriver.HasRomLoaded()) {
|
if (AlberDriver.HasRomLoaded()) {
|
||||||
AlberDriver.Finalize();
|
AlberDriver.Finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.panda3ds.pandroid.app;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
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 GamesFragment gamesFragment = new GamesFragment();
|
||||||
private final SearchFragment searchFragment = new SearchFragment();
|
private final SearchFragment searchFragment = new SearchFragment();
|
||||||
private final SettingsFragment settingsFragment = new SettingsFragment();
|
private final SettingsFragment settingsFragment = new SettingsFragment();
|
||||||
|
private NavigationBarView navigationBar;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
NavigationBarView bar = findViewById(R.id.navigation);
|
navigationBar = findViewById(R.id.navigation);
|
||||||
bar.setOnItemSelectedListener(this);
|
navigationBar.setOnItemSelectedListener(this);
|
||||||
bar.postDelayed(() -> bar.setSelectedItemId(bar.getSelectedItemId()), 5);
|
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
|
@Override
|
||||||
|
|
|
@ -7,7 +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.SwitchPreference;
|
import androidx.preference.SwitchPreferenceCompat;
|
||||||
|
|
||||||
import com.panda3ds.pandroid.lang.Function;
|
import com.panda3ds.pandroid.lang.Function;
|
||||||
|
|
||||||
|
@ -22,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) {
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.panda3ds.pandroid.app.base;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
import com.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.utils.CompatUtils;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class BaseSheetDialog extends BottomSheetDialog {
|
||||||
|
private final LinearLayout contentView;
|
||||||
|
public BaseSheetDialog(@NonNull Context context) {
|
||||||
|
super(CompatUtils.findActivity(context));
|
||||||
|
int width = CompatUtils.findActivity(context).getWindow().getDecorView().getMeasuredWidth();
|
||||||
|
int height = CompatUtils.findActivity(context).getWindow().getDecorView().getMeasuredHeight();
|
||||||
|
getBehavior().setPeekHeight((int) (height*0.87));
|
||||||
|
getBehavior().setMaxWidth(width);
|
||||||
|
getBehavior().setMaxHeight((int) (height*0.87));
|
||||||
|
super.setContentView(R.layout.dialog_bottom_sheet);
|
||||||
|
contentView = super.findViewById(R.id.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContentView(View view) {
|
||||||
|
contentView.removeAllViews();
|
||||||
|
contentView.addView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContentView(int layoutResId) {
|
||||||
|
setContentView(LayoutInflater.from(getContext()).inflate(layoutResId, null, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public <T extends View> T findViewById(int id) {
|
||||||
|
return contentView.findViewById(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.panda3ds.pandroid.app.base;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||||
|
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||||
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
|
|
||||||
|
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.utils.CompatUtils;
|
||||||
|
import com.panda3ds.pandroid.utils.FileUtils;
|
||||||
|
import com.panda3ds.pandroid.utils.GameUtils;
|
||||||
|
import com.panda3ds.pandroid.view.gamesgrid.GameIconView;
|
||||||
|
|
||||||
|
public class GameAboutDialog extends BaseSheetDialog {
|
||||||
|
private final GameMetadata game;
|
||||||
|
public GameAboutDialog(@NonNull Context context, GameMetadata game) {
|
||||||
|
super(context);
|
||||||
|
this.game = game;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.dialog_game_about);
|
||||||
|
|
||||||
|
((GameIconView) findViewById(R.id.game_icon)).setImageBitmap(game.getIcon());
|
||||||
|
((TextView) findViewById(R.id.game_title)).setText(game.getTitle());
|
||||||
|
((TextView) findViewById(R.id.game_publisher)).setText(game.getPublisher());
|
||||||
|
((TextView) findViewById(R.id.region)).setText(game.getRegions()[0].localizedName());
|
||||||
|
((TextView) findViewById(R.id.directory)).setText(FileUtils.obtainUri(game.getRealPath()).getPath());
|
||||||
|
findViewById(R.id.play).setOnClickListener(v -> {
|
||||||
|
dismiss();
|
||||||
|
GameUtils.launch(getContext(), game);
|
||||||
|
});
|
||||||
|
findViewById(R.id.shortcut).setOnClickListener(v -> {
|
||||||
|
dismiss();
|
||||||
|
makeShortcut();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (game.getRomPath().startsWith("folder:")) {
|
||||||
|
findViewById(R.id.remove).setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
findViewById(R.id.remove).setOnClickListener(v -> {
|
||||||
|
dismiss();
|
||||||
|
if (game.getRomPath().startsWith("elf:")) {
|
||||||
|
FileUtils.delete(game.getRealPath());
|
||||||
|
}
|
||||||
|
GameUtils.removeGame(game);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,8 +11,8 @@ import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
public class AlberInputListener implements Function<InputEvent> {
|
public class AlberInputListener implements Function<InputEvent> {
|
||||||
private final Runnable backListener;
|
private final EmulatorCallback emulator;
|
||||||
public AlberInputListener(Runnable backListener) { this.backListener = backListener; }
|
public AlberInputListener(EmulatorCallback emulator) { this.emulator = emulator; }
|
||||||
|
|
||||||
private final Vector2 axis = new Vector2(0.0f, 0.0f);
|
private final Vector2 axis = new Vector2(0.0f, 0.0f);
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ public class AlberInputListener implements Function<InputEvent> {
|
||||||
KeyName key = InputMap.relative(event.getName());
|
KeyName key = InputMap.relative(event.getName());
|
||||||
|
|
||||||
if (Objects.equals(event.getName(), "KEYCODE_BACK")) {
|
if (Objects.equals(event.getName(), "KEYCODE_BACK")) {
|
||||||
backListener.run();
|
emulator.onBackPressed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,11 @@ public class AlberInputListener implements Function<InputEvent> {
|
||||||
axis.x = event.getValue();
|
axis.x = event.getValue();
|
||||||
axisChanged = true;
|
axisChanged = true;
|
||||||
break;
|
break;
|
||||||
|
case CHANGE_DS_LAYOUT:
|
||||||
|
if (!event.isDown()){
|
||||||
|
emulator.swapScreens();
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if (event.isDown()) {
|
if (event.isDown()) {
|
||||||
AlberDriver.KeyDown(key.getKeyId());
|
AlberDriver.KeyDown(key.getKeyId());
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.panda3ds.pandroid.app.game;
|
package com.panda3ds.pandroid.app.game;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
@ -13,7 +15,6 @@ import androidx.appcompat.widget.AppCompatTextView;
|
||||||
import androidx.drawerlayout.widget.DrawerLayout;
|
import androidx.drawerlayout.widget.DrawerLayout;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.content.res.Configuration;
|
|
||||||
|
|
||||||
import com.google.android.material.navigation.NavigationView;
|
import com.google.android.material.navigation.NavigationView;
|
||||||
import com.panda3ds.pandroid.AlberDriver;
|
import com.panda3ds.pandroid.AlberDriver;
|
||||||
|
@ -24,10 +25,14 @@ 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 GameMetadata game;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
emulator = ((EmulatorCallback) requireActivity());
|
||||||
drawerContainer = requireActivity().findViewById(R.id.drawer_container);
|
drawerContainer = requireActivity().findViewById(R.id.drawer_container);
|
||||||
drawerContainer.removeDrawerListener(this);
|
drawerContainer.removeDrawerListener(this);
|
||||||
drawerContainer.addDrawerListener(this);
|
drawerContainer.addDrawerListener(this);
|
||||||
|
@ -41,14 +46,22 @@ 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);
|
||||||
GameMetadata game = GameUtils.getCurrentGame();
|
|
||||||
|
|
||||||
((GameIconView)view.findViewById(R.id.game_icon)).setImageBitmap(game.getIcon());
|
|
||||||
((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);
|
((NavigationView)view.findViewById(R.id.menu)).setNavigationItemSelectedListener(this);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh(){
|
||||||
|
game = GameUtils.getCurrentGame();
|
||||||
|
if (game.getIcon() != null && !game.getIcon().isRecycled()) {
|
||||||
|
((GameIconView) drawerLayout.findViewById(R.id.game_icon)).setImageBitmap(game.getIcon());
|
||||||
|
} else {
|
||||||
|
((GameIconView) drawerLayout.findViewById(R.id.game_icon)).setImageDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||||
|
}
|
||||||
|
((AppCompatTextView)drawerLayout.findViewById(R.id.game_title)).setText(game.getTitle());
|
||||||
|
((AppCompatTextView)drawerLayout.findViewById(R.id.game_publisher)).setText(game.getPublisher());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,6 +85,7 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe
|
||||||
drawerContainer.setVisibility(View.VISIBLE);
|
drawerContainer.setVisibility(View.VISIBLE);
|
||||||
drawerContainer.open();
|
drawerContainer.open();
|
||||||
drawerContainer.postDelayed(this::refreshLayout, 20);
|
drawerContainer.postDelayed(this::refreshLayout, 20);
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,8 +117,11 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
if (id == R.id.resume) {
|
if (id == R.id.resume) {
|
||||||
close();
|
close();
|
||||||
|
} else if (id == R.id.ds_switch) {
|
||||||
|
emulator.swapScreens();
|
||||||
|
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) {
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.panda3ds.pandroid.app.game;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
|
|
||||||
|
public interface EmulatorCallback {
|
||||||
|
void onBackPressed();
|
||||||
|
void swapScreens(int index);
|
||||||
|
default void swapScreens() {
|
||||||
|
swapScreens(GlobalConfig.get(GlobalConfig.KEY_CURRENT_DS_LAYOUT)+1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import com.panda3ds.pandroid.R;
|
import com.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.app.base.GameAboutDialog;
|
||||||
import com.panda3ds.pandroid.app.base.LoadingAlertDialog;
|
import com.panda3ds.pandroid.app.base.LoadingAlertDialog;
|
||||||
import com.panda3ds.pandroid.data.game.GameMetadata;
|
import com.panda3ds.pandroid.data.game.GameMetadata;
|
||||||
import com.panda3ds.pandroid.lang.Task;
|
import com.panda3ds.pandroid.lang.Task;
|
||||||
|
@ -38,6 +39,13 @@ public class GamesFragment extends Fragment implements ActivityResultCallback<Ur
|
||||||
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);
|
||||||
gameListView = view.findViewById(R.id.games);
|
gameListView = view.findViewById(R.id.games);
|
||||||
|
gameListView.setItemLongClick((game)->{
|
||||||
|
GameAboutDialog dialog = new GameAboutDialog(requireActivity(), game);
|
||||||
|
dialog.setOnDismissListener((x)-> {
|
||||||
|
gameListView.setGameList(GameUtils.getGames());
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
|
});
|
||||||
|
|
||||||
view.findViewById(R.id.add_rom).setOnClickListener((v) -> pickFileRequest.launch(new String[] {"*/*"}));
|
view.findViewById(R.id.add_rom).setOnClickListener((v) -> pickFileRequest.launch(new String[] {"*/*"}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.AppCompatEditText;
|
import androidx.appcompat.widget.AppCompatEditText;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import com.panda3ds.pandroid.R;
|
import com.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.app.base.GameAboutDialog;
|
||||||
import com.panda3ds.pandroid.data.game.GameMetadata;
|
import com.panda3ds.pandroid.data.game.GameMetadata;
|
||||||
import com.panda3ds.pandroid.utils.GameUtils;
|
import com.panda3ds.pandroid.utils.GameUtils;
|
||||||
import com.panda3ds.pandroid.utils.SearchAgent;
|
import com.panda3ds.pandroid.utils.SearchAgent;
|
||||||
|
@ -33,6 +34,11 @@ public class SearchFragment extends Fragment {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
gamesListView = view.findViewById(R.id.games);
|
gamesListView = view.findViewById(R.id.games);
|
||||||
|
gamesListView.setItemLongClick((game)->{
|
||||||
|
GameAboutDialog dialog = new GameAboutDialog(requireActivity(), game);
|
||||||
|
dialog.setOnDismissListener((x)-> search(((AppCompatEditText) view.findViewById(R.id.search_bar)).getText().toString()));
|
||||||
|
dialog.show();
|
||||||
|
});
|
||||||
((AppCompatEditText) view.findViewById(R.id.search_bar)).addTextChangedListener((SimpleTextWatcher) this::search);
|
((AppCompatEditText) view.findViewById(R.id.search_bar)).addTextChangedListener((SimpleTextWatcher) this::search);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package com.panda3ds.pandroid.app.main;
|
package com.panda3ds.pandroid.app.main;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
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.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.AppearancePreferences;
|
import com.panda3ds.pandroid.app.preferences.GeneralPreferences;
|
||||||
import com.panda3ds.pandroid.app.preferences.AdvancedPreferences;
|
import com.panda3ds.pandroid.app.preferences.AdvancedPreferences;
|
||||||
import com.panda3ds.pandroid.app.preferences.InputPreferences;
|
import com.panda3ds.pandroid.app.preferences.InputPreferences;
|
||||||
|
|
||||||
|
@ -15,8 +17,18 @@ public class SettingsFragment 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.start_preferences, rootKey);
|
setPreferencesFromResource(R.xml.start_preferences, rootKey);
|
||||||
|
findPreference("application").setSummary(getVersionName());
|
||||||
setItemClick("input", (item) -> PreferenceActivity.launch(requireContext(), InputPreferences.class));
|
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));
|
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 "???";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ package com.panda3ds.pandroid.app.preferences;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.preference.SwitchPreference;
|
import androidx.preference.SwitchPreferenceCompat;
|
||||||
|
|
||||||
import com.panda3ds.pandroid.R;
|
import com.panda3ds.pandroid.R;
|
||||||
import com.panda3ds.pandroid.app.PandroidApplication;
|
import com.panda3ds.pandroid.app.PandroidApplication;
|
||||||
|
@ -19,10 +20,10 @@ public class AdvancedPreferences extends BasePreferenceFragment {
|
||||||
setPreferencesFromResource(R.xml.advanced_preferences, rootKey);
|
setPreferencesFromResource(R.xml.advanced_preferences, rootKey);
|
||||||
setActivityTitle(R.string.advanced_options);
|
setActivityTitle(R.string.advanced_options);
|
||||||
|
|
||||||
setItemClick("performanceMonitor", pref -> GlobalConfig.set(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY, ((SwitchPreference) pref).isChecked()));
|
setItemClick("performanceMonitor", pref -> GlobalConfig.set(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY, ((SwitchPreferenceCompat) pref).isChecked()));
|
||||||
setItemClick("shaderJit", pref -> GlobalConfig.set(GlobalConfig.KEY_SHADER_JIT, ((SwitchPreference) pref).isChecked()));
|
setItemClick("shaderJit", pref -> GlobalConfig.set(GlobalConfig.KEY_SHADER_JIT, ((SwitchPreferenceCompat) pref).isChecked()));
|
||||||
setItemClick("loggerService", pref -> {
|
setItemClick("loggerService", pref -> {
|
||||||
boolean checked = ((SwitchPreference) pref).isChecked();
|
boolean checked = ((SwitchPreferenceCompat) pref).isChecked();
|
||||||
Context ctx = PandroidApplication.getAppContext();
|
Context ctx = PandroidApplication.getAppContext();
|
||||||
if (checked) {
|
if (checked) {
|
||||||
ctx.startService(new Intent(ctx, LoggerService.class));
|
ctx.startService(new Intent(ctx, LoggerService.class));
|
||||||
|
@ -42,8 +43,8 @@ public class AdvancedPreferences extends BasePreferenceFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
((SwitchPreference) findPreference("performanceMonitor")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY));
|
((SwitchPreferenceCompat) findPreference("performanceMonitor")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY));
|
||||||
((SwitchPreference) findPreference("loggerService")).setChecked(GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE));
|
((SwitchPreferenceCompat) findPreference("loggerService")).setChecked(GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE));
|
||||||
((SwitchPreference) findPreference("shaderJit")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT));
|
((SwitchPreferenceCompat) findPreference("shaderJit")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package com.panda3ds.pandroid.app.preferences;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultCallback;
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
|
||||||
|
import com.panda3ds.pandroid.app.base.BaseSheetDialog;
|
||||||
|
import com.panda3ds.pandroid.data.game.GamesFolder;
|
||||||
|
import com.panda3ds.pandroid.utils.FileUtils;
|
||||||
|
import com.panda3ds.pandroid.utils.GameUtils;
|
||||||
|
|
||||||
|
public class GamesFoldersPreferences extends BasePreferenceFragment implements ActivityResultCallback<Uri> {
|
||||||
|
private final ActivityResultContracts.OpenDocumentTree openFolderContract = new ActivityResultContracts.OpenDocumentTree();
|
||||||
|
private ActivityResultLauncher<Uri> pickFolderRequest;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
|
||||||
|
setPreferencesFromResource(R.xml.empty_preferences, rootKey);
|
||||||
|
setActivityTitle(R.string.pref_game_folders);
|
||||||
|
refreshList();
|
||||||
|
pickFolderRequest = registerForActivityResult(openFolderContract, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
private void refreshList() {
|
||||||
|
GamesFolder[] folders = GameUtils.getFolders();
|
||||||
|
PreferenceScreen screen = getPreferenceScreen();
|
||||||
|
screen.removeAll();
|
||||||
|
for (GamesFolder folder : folders) {
|
||||||
|
Preference preference = new Preference(screen.getContext());
|
||||||
|
preference.setOnPreferenceClickListener((item) -> {
|
||||||
|
showFolderInfo(folder);
|
||||||
|
screen.performClick();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
preference.setTitle(FileUtils.getName(folder.getPath()));
|
||||||
|
preference.setSummary(String.format(getString(R.string.games_count_f), folder.getGames().size()));
|
||||||
|
preference.setIcon(R.drawable.ic_folder);
|
||||||
|
screen.addPreference(preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
Preference add = new Preference(screen.getContext());
|
||||||
|
add.setTitle(R.string.import_folder);
|
||||||
|
add.setIcon(R.drawable.ic_add);
|
||||||
|
add.setOnPreferenceClickListener(preference -> {
|
||||||
|
pickFolderRequest.launch(null);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
screen.addPreference(add);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showFolderInfo(GamesFolder folder) {
|
||||||
|
BaseSheetDialog dialog = new BaseSheetDialog(requireActivity());
|
||||||
|
View layout = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_games_folder, null, false);
|
||||||
|
dialog.setContentView(layout);
|
||||||
|
|
||||||
|
((TextView) layout.findViewById(R.id.name)).setText(FileUtils.getName(folder.getPath()));
|
||||||
|
((TextView) layout.findViewById(R.id.directory)).setText(FileUtils.obtainUri(folder.getPath()).getPath());
|
||||||
|
((TextView) layout.findViewById(R.id.games)).setText(String.valueOf(folder.getGames().size()));
|
||||||
|
|
||||||
|
layout.findViewById(R.id.ok).setOnClickListener(v -> dialog.dismiss());
|
||||||
|
layout.findViewById(R.id.remove).setOnClickListener(v -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
GameUtils.removeFolder(folder);
|
||||||
|
refreshList();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (pickFolderRequest != null) {
|
||||||
|
pickFolderRequest.unregister();
|
||||||
|
pickFolderRequest = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(Uri result) {
|
||||||
|
if (result != null) {
|
||||||
|
FileUtils.makeUriPermanent(result.toString(), "r");
|
||||||
|
GameUtils.registerFolder(result.toString());
|
||||||
|
refreshList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.panda3ds.pandroid.app.preferences;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.preference.SwitchPreferenceCompat;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.app.PreferenceActivity;
|
||||||
|
import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
|
||||||
|
import com.panda3ds.pandroid.app.preferences.screen_editor.ScreenLayoutsPreference;
|
||||||
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
|
|
||||||
|
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(), ScreenLayoutsPreference.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);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh(){
|
||||||
|
setSwitchValue("behavior.pictureInPicture", GlobalConfig.get(GlobalConfig.KEY_PICTURE_IN_PICTURE));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.panda3ds.pandroid.app.preferences;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
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.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.app.base.BaseSheetDialog;
|
||||||
|
import com.panda3ds.pandroid.utils.CompatUtils;
|
||||||
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
|
import com.panda3ds.pandroid.view.recycler.AutoFitGridLayout;
|
||||||
|
import com.panda3ds.pandroid.view.recycler.SimpleListAdapter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class ThemeSelectorDialog extends BaseSheetDialog {
|
||||||
|
|
||||||
|
private final SimpleListAdapter<Theme> adapter = new SimpleListAdapter<>(R.layout.hold_theme_preview_base, this::bindItemView);
|
||||||
|
private final int currentTheme = GlobalConfig.get(GlobalConfig.KEY_APP_THEME);
|
||||||
|
private static final ArrayList<Theme> themes = new ArrayList<>(Arrays.asList(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.dialog_select_theme);
|
||||||
|
adapter.clear();
|
||||||
|
themes.sort((o1, o2) -> o1.value == currentTheme ? -1 : 0);
|
||||||
|
adapter.addAll(themes);
|
||||||
|
|
||||||
|
RecyclerView recycler = findViewById(R.id.recycler);
|
||||||
|
recycler.setAdapter(adapter);
|
||||||
|
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.value);
|
||||||
|
view.setOnClickListener(v -> {
|
||||||
|
dismiss();
|
||||||
|
if (theme.value != GlobalConfig.get(GlobalConfig.KEY_APP_THEME)) {
|
||||||
|
GlobalConfig.set(GlobalConfig.KEY_APP_THEME, theme.value);
|
||||||
|
CompatUtils.findActivity(getContext()).recreate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Theme {
|
||||||
|
private final int style;
|
||||||
|
private final int name;
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
private Theme(int style, int name, int value) {
|
||||||
|
this.style = style;
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.panda3ds.pandroid.app.preferences.screen_editor;
|
||||||
|
|
||||||
|
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 ScreenEditorPreference extends Fragment {
|
||||||
|
private LinearLayout layout;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
layout = new LinearLayout(container.getContext());
|
||||||
|
layout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_FULLSCREEN|View.SYSTEM_UI_FLAG_IMMERSIVE);
|
||||||
|
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(new DsEditorView(view.getContext(), index), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
((BaseActivity)requireActivity()).getSupportActionBar().hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
DsLayoutManager.save();
|
||||||
|
Toast.makeText(requireActivity(), R.string.saved, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.panda3ds.pandroid.app.preferences.screen_editor;
|
||||||
|
|
||||||
|
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 ScreenLayoutsPreference 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(), ScreenEditorPreference.class, new Intent().putExtra("index", index));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
screen.addPreference(pref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
package com.panda3ds.pandroid.app.provider;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.os.CancellationSignal;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.DocumentsContract.Document;
|
||||||
|
import android.provider.DocumentsContract.Root;
|
||||||
|
import android.provider.DocumentsProvider;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.app.PandroidApplication;
|
||||||
|
import com.panda3ds.pandroid.utils.FileUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class AppDataDocumentProvider extends DocumentsProvider {
|
||||||
|
private static final String ROOT_ID = "root";
|
||||||
|
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
|
||||||
|
Root.COLUMN_ROOT_ID,
|
||||||
|
Root.COLUMN_MIME_TYPES,
|
||||||
|
Root.COLUMN_FLAGS,
|
||||||
|
Root.COLUMN_ICON,
|
||||||
|
Root.COLUMN_TITLE,
|
||||||
|
Root.COLUMN_SUMMARY,
|
||||||
|
Root.COLUMN_DOCUMENT_ID,
|
||||||
|
Root.COLUMN_AVAILABLE_BYTES
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
|
||||||
|
Document.COLUMN_DOCUMENT_ID,
|
||||||
|
Document.COLUMN_DISPLAY_NAME,
|
||||||
|
Document.COLUMN_MIME_TYPE,
|
||||||
|
Document.COLUMN_LAST_MODIFIED,
|
||||||
|
Document.COLUMN_SIZE
|
||||||
|
};
|
||||||
|
|
||||||
|
private String obtainDocumentId(File file) {
|
||||||
|
String basePath = baseDirectory().getAbsolutePath();
|
||||||
|
String fullPath = file.getAbsolutePath();
|
||||||
|
return (ROOT_ID + "/" + fullPath.substring(basePath.length())).replaceAll("//", "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private File obtainFile(String documentId) {
|
||||||
|
if (documentId.startsWith(ROOT_ID)) {
|
||||||
|
return new File(baseDirectory(), documentId.substring(ROOT_ID.length()));
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Invalid document id: " + documentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Context context() {
|
||||||
|
return PandroidApplication.getAppContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private File baseDirectory() {
|
||||||
|
return context().getFilesDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||||
|
MatrixCursor cursor = new MatrixCursor(projection == null ? DEFAULT_ROOT_PROJECTION : projection);
|
||||||
|
cursor.newRow()
|
||||||
|
.add(Root.COLUMN_ROOT_ID, ROOT_ID)
|
||||||
|
.add(Root.COLUMN_SUMMARY, null)
|
||||||
|
.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE)
|
||||||
|
.add(Root.COLUMN_DOCUMENT_ID, ROOT_ID + "/")
|
||||||
|
.add(Root.COLUMN_AVAILABLE_BYTES, baseDirectory().getFreeSpace())
|
||||||
|
.add(Root.COLUMN_TITLE, context().getString(R.string.app_name))
|
||||||
|
.add(Root.COLUMN_MIME_TYPES, "*/*")
|
||||||
|
.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||||
|
File file = obtainFile(documentId);
|
||||||
|
MatrixCursor cursor = new MatrixCursor(projection == null ? DEFAULT_DOCUMENT_PROJECTION : projection);
|
||||||
|
includeFile(cursor, file);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void includeFile(MatrixCursor cursor, File file) {
|
||||||
|
int flags = 0;
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
flags = Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||||
|
} else {
|
||||||
|
flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_REMOVE | Document.FLAG_SUPPORTS_DELETE;
|
||||||
|
}
|
||||||
|
cursor.newRow()
|
||||||
|
.add(Document.COLUMN_DOCUMENT_ID, obtainDocumentId(file))
|
||||||
|
.add(Document.COLUMN_MIME_TYPE, file.isDirectory() ? Document.MIME_TYPE_DIR : "application/octect-stream")
|
||||||
|
.add(Document.COLUMN_FLAGS, flags)
|
||||||
|
.add(Document.COLUMN_LAST_MODIFIED, file.lastModified())
|
||||||
|
.add(Document.COLUMN_DISPLAY_NAME, file.getName())
|
||||||
|
.add(Document.COLUMN_SIZE, file.length());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||||
|
File file = obtainFile(parentDocumentId);
|
||||||
|
MatrixCursor cursor = new MatrixCursor(projection == null ? DEFAULT_DOCUMENT_PROJECTION : projection);
|
||||||
|
File[] children = file.listFiles();
|
||||||
|
if (children != null) {
|
||||||
|
for (File child : children) {
|
||||||
|
includeFile(cursor, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
|
||||||
|
return ParcelFileDescriptor.open(obtainFile(documentId), ParcelFileDescriptor.parseMode(mode));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package com.panda3ds.pandroid.data;
|
package com.panda3ds.pandroid.data;
|
||||||
|
|
||||||
|
import com.google.gson.ExclusionStrategy;
|
||||||
|
import com.google.gson.FieldAttributes;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.panda3ds.pandroid.lang.Task;
|
import com.panda3ds.pandroid.lang.Task;
|
||||||
|
@ -30,7 +32,10 @@ public class GsonConfigParser {
|
||||||
String[] content = new String[] {"{}"};
|
String[] content = new String[] {"{}"};
|
||||||
new Task(()->{
|
new Task(()->{
|
||||||
if (FileUtils.exists(getPath())) {
|
if (FileUtils.exists(getPath())) {
|
||||||
content[0] = FileUtils.readTextFile(getPath());
|
String src = FileUtils.readTextFile(getPath());
|
||||||
|
if(src != null && src.length() > 2){
|
||||||
|
content[0] = src;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).runSync();
|
}).runSync();
|
||||||
|
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.panda3ds.pandroid.data.config;
|
package com.panda3ds.pandroid.data.config;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.internal.LinkedTreeMap;
|
import com.google.gson.internal.LinkedTreeMap;
|
||||||
import com.panda3ds.pandroid.data.GsonConfigParser;
|
import com.panda3ds.pandroid.data.GsonConfigParser;
|
||||||
import com.panda3ds.pandroid.utils.Constants;
|
import com.panda3ds.pandroid.utils.Constants;
|
||||||
|
@ -10,6 +12,7 @@ import java.util.Map;
|
||||||
public class GlobalConfig {
|
public class GlobalConfig {
|
||||||
|
|
||||||
private static final GsonConfigParser parser = new GsonConfigParser(Constants.PREF_GLOBAL_CONFIG);
|
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_ANDROID = 0;
|
||||||
public static final int THEME_LIGHT = 1;
|
public static final int THEME_LIGHT = 1;
|
||||||
|
@ -19,10 +22,13 @@ 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_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 void initialize() {
|
public static void initialize() {
|
||||||
data = parser.load(DataModel.class);
|
data = parser.load(DataModel.class);
|
||||||
|
@ -54,6 +60,21 @@ public class GlobalConfig {
|
||||||
writeChanges();
|
writeChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T extends Object> T getExtra(Key<String> key, Class<T> 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<String> 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() {
|
private static void writeChanges() {
|
||||||
parser.save(data);
|
parser.save(data);
|
||||||
}
|
}
|
||||||
|
@ -70,6 +91,7 @@ public class GlobalConfig {
|
||||||
|
|
||||||
private static class DataModel {
|
private static class DataModel {
|
||||||
private final Map<String, Object> configs = new LinkedTreeMap<>();
|
private final Map<String, Object> configs = new LinkedTreeMap<>();
|
||||||
|
private final JsonObject extras = new JsonObject();
|
||||||
|
|
||||||
public Object get(String key) {
|
public Object get(String key) {
|
||||||
return configs.get(key);
|
return configs.get(key);
|
||||||
|
|
|
@ -15,9 +15,9 @@ import java.util.UUID;
|
||||||
public class GameMetadata {
|
public class GameMetadata {
|
||||||
private final String id;
|
private final String id;
|
||||||
private final String romPath;
|
private final String romPath;
|
||||||
private final String title;
|
private String title;
|
||||||
private final String publisher;
|
private String publisher;
|
||||||
private final GameRegion[] regions;
|
private GameRegion[] regions;
|
||||||
private transient Bitmap icon;
|
private transient Bitmap icon;
|
||||||
|
|
||||||
private GameMetadata(String id, String romPath, String title, String publisher, Bitmap icon, GameRegion[] regions) {
|
private GameMetadata(String id, String romPath, String title, String publisher, Bitmap icon, GameRegion[] regions) {
|
||||||
|
@ -60,7 +60,7 @@ public class GameMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap getIcon() {
|
public Bitmap getIcon() {
|
||||||
if (icon == null) {
|
if (icon == null || icon.isRecycled()) {
|
||||||
icon = GameUtils.loadGameIcon(id);
|
icon = GameUtils.loadGameIcon(id);
|
||||||
}
|
}
|
||||||
return icon;
|
return icon;
|
||||||
|
@ -78,10 +78,15 @@ public class GameMetadata {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameMetadata applySMDH(GameMetadata meta, SMDH smdh) {
|
public void applySMDH(SMDH smdh) {
|
||||||
Bitmap icon = smdh.getBitmapIcon();
|
Bitmap icon = smdh.getBitmapIcon();
|
||||||
GameMetadata newMeta = new GameMetadata(meta.getId(), meta.getRomPath(), smdh.getTitle(), smdh.getPublisher(), icon, new GameRegion[]{smdh.getRegion()});
|
this.title = smdh.getTitle();
|
||||||
icon.recycle();
|
this.publisher = smdh.getPublisher();
|
||||||
return newMeta;
|
this.icon = icon;
|
||||||
|
if (icon != null){
|
||||||
|
GameUtils.setGameIcon(id, icon);
|
||||||
|
}
|
||||||
|
this.regions = new GameRegion[]{smdh.getRegion()};
|
||||||
|
GameUtils.writeChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.panda3ds.pandroid.data.game;
|
package com.panda3ds.pandroid.data.game;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.R;
|
||||||
|
|
||||||
public enum GameRegion {
|
public enum GameRegion {
|
||||||
NorthAmerican,
|
NorthAmerican,
|
||||||
Japan,
|
Japan,
|
||||||
|
@ -8,5 +10,23 @@ public enum GameRegion {
|
||||||
China,
|
China,
|
||||||
Korean,
|
Korean,
|
||||||
Taiwan,
|
Taiwan,
|
||||||
None
|
None;
|
||||||
|
|
||||||
|
public int localizedName(){
|
||||||
|
switch (this){
|
||||||
|
case NorthAmerican:
|
||||||
|
return R.string.region_north_armerican;
|
||||||
|
case Japan:
|
||||||
|
return R.string.region_japan;
|
||||||
|
case Europe:
|
||||||
|
return R.string.region_europe;
|
||||||
|
case Australia:
|
||||||
|
return R.string.region_australia;
|
||||||
|
case Korean:
|
||||||
|
return R.string.region_korean;
|
||||||
|
case Taiwan:
|
||||||
|
return R.string.region_taiwan;
|
||||||
|
}
|
||||||
|
return R.string.unknown;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.panda3ds.pandroid.data.game;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.app.PandroidApplication;
|
||||||
|
import com.panda3ds.pandroid.utils.FileUtils;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class GamesFolder {
|
||||||
|
|
||||||
|
private final String id = UUID.randomUUID().toString();
|
||||||
|
private final String path;
|
||||||
|
private final HashMap<String, GameMetadata> games = new HashMap<>();
|
||||||
|
|
||||||
|
public GamesFolder(String path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid(){
|
||||||
|
return FileUtils.exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<GameMetadata> getGames() {
|
||||||
|
return games.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
String[] gamesId = games.keySet().toArray(new String[0]);
|
||||||
|
for (String file: gamesId){
|
||||||
|
if (!FileUtils.exists(path+"/"+file)){
|
||||||
|
games.remove(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String unknown = PandroidApplication.getAppContext().getString(R.string.unknown);
|
||||||
|
|
||||||
|
for (String file: FileUtils.listFiles(path)){
|
||||||
|
String path = FileUtils.getChild(this.path, file);
|
||||||
|
if (FileUtils.isDirectory(path) || games.containsKey(file)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String ext = FileUtils.extension(path);
|
||||||
|
if (ext.equals("3ds") || ext.equals("3dsx")){
|
||||||
|
String name = FileUtils.getName(path).trim().split("\\.")[0];
|
||||||
|
games.put(file, new GameMetadata(new Uri.Builder().path(file).authority(id).scheme("folder").build().toString(),name, unknown));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ public class InputHandler {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final HashMap<String, Float> motionDownEvents = new HashMap<>();
|
private static final HashMap<String, Float> motionDownEvents = new HashMap<>();
|
||||||
|
private static final HashMap<String, InputEvent> keyDownEvents = new HashMap<>();
|
||||||
|
|
||||||
private static boolean containsSource(int[] sources, int sourceMask) {
|
private static boolean containsSource(int[] sources, int sourceMask) {
|
||||||
for (int source : sources) {
|
for (int source : sources) {
|
||||||
|
@ -108,8 +109,17 @@ public class InputHandler {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
handleEvent(new InputEvent(KeyEvent.keyCodeToString(event.getKeyCode()), event.getAction() == KeyEvent.ACTION_UP ? 0.0f : 1.0f));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,5 +127,6 @@ public class InputHandler {
|
||||||
eventListener = null;
|
eventListener = null;
|
||||||
motionDeadZone = 0.0f;
|
motionDeadZone = 0.0f;
|
||||||
motionDownEvents.clear();
|
motionDownEvents.clear();
|
||||||
|
keyDownEvents.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,8 @@ public enum KeyName {
|
||||||
SELECT(Constants.INPUT_KEY_SELECT),
|
SELECT(Constants.INPUT_KEY_SELECT),
|
||||||
L(Constants.INPUT_KEY_L),
|
L(Constants.INPUT_KEY_L),
|
||||||
R(Constants.INPUT_KEY_R),
|
R(Constants.INPUT_KEY_R),
|
||||||
NULL;
|
NULL,
|
||||||
|
CHANGE_DS_LAYOUT;
|
||||||
|
|
||||||
private final int keyId;
|
private final int keyId;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.panda3ds.pandroid.utils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
|
import androidx.annotation.AttrRes;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.app.PandroidApplication;
|
||||||
|
|
||||||
|
public class CompatUtils {
|
||||||
|
public static Activity findActivity(Context context) {
|
||||||
|
if (context instanceof Activity) {
|
||||||
|
return (Activity) context;
|
||||||
|
} else if ((context instanceof ContextWrapper)) {
|
||||||
|
return findActivity(((ContextWrapper) context).getBaseContext());
|
||||||
|
}
|
||||||
|
return ((Activity) context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int resolveColor(Context context, @AttrRes int id){
|
||||||
|
try {
|
||||||
|
TypedArray values = context.obtainStyledAttributes(new int[]{id});
|
||||||
|
int color = values.getColor(0, Color.RED);
|
||||||
|
values.recycle();
|
||||||
|
return color;
|
||||||
|
} catch (Exception e){
|
||||||
|
return Color.rgb(255,0,255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float applyDimen(int unit, int size) {
|
||||||
|
return TypedValue.applyDimension(unit, size, PandroidApplication.getAppContext().getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,10 @@ package com.panda3ds.pandroid.utils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
@ -21,14 +23,21 @@ import java.util.Objects;
|
||||||
|
|
||||||
public class FileUtils {
|
public class FileUtils {
|
||||||
public static final String MODE_READ = "r";
|
public static final String MODE_READ = "r";
|
||||||
public static final int CANONICAL_SEARCH_DEEP = 8;
|
private static final String TREE_URI = "tree";
|
||||||
|
|
||||||
private static DocumentFile parseFile(String path) {
|
private static DocumentFile parseFile(String path) {
|
||||||
if (path.startsWith("/")) {
|
if (path.startsWith("/")) {
|
||||||
return DocumentFile.fromFile(new File(path));
|
return DocumentFile.fromFile(new File(path));
|
||||||
}
|
}
|
||||||
Uri uri = Uri.parse(path);
|
Uri uri = Uri.parse(path);
|
||||||
return DocumentFile.fromSingleUri(getContext(), uri);
|
DocumentFile singleFile = DocumentFile.fromSingleUri(getContext(), uri);
|
||||||
|
if (singleFile.length() > 0 && singleFile.length() != 4096){
|
||||||
|
return singleFile;
|
||||||
|
}
|
||||||
|
if (uri.getScheme().equals("content") && uri.getPath().startsWith("/"+TREE_URI)){
|
||||||
|
return DocumentFile.fromTreeUri(getContext(), uri);
|
||||||
|
}
|
||||||
|
return singleFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Context getContext() {
|
private static Context getContext() {
|
||||||
|
@ -194,47 +203,6 @@ public class FileUtils {
|
||||||
getContext().getContentResolver().takePersistableUriPermission(Uri.parse(uri), flags);
|
getContext().getContentResolver().takePersistableUriPermission(Uri.parse(uri), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* When call ContentProvider.openFileDescriptor() android opens a file descriptor
|
|
||||||
* on app process in /proc/self/fd/[file descriptor id] this is a link to real file path
|
|
||||||
* can use File.getCanonicalPath() for get a link origin, but in some android version
|
|
||||||
* need use Os.readlink(path) to get a real path.
|
|
||||||
*/
|
|
||||||
public static String obtainRealPath(String uri) {
|
|
||||||
try {
|
|
||||||
ParcelFileDescriptor parcelDescriptor = getContext().getContentResolver().openFileDescriptor(Uri.parse(uri), "r");
|
|
||||||
int fd = parcelDescriptor.getFd();
|
|
||||||
File file = new File("/proc/self/fd/" + fd).getAbsoluteFile();
|
|
||||||
|
|
||||||
for (int i = 0; i < CANONICAL_SEARCH_DEEP; i++) {
|
|
||||||
try {
|
|
||||||
String canonical = file.getCanonicalPath();
|
|
||||||
if (!Objects.equals(canonical, file.getAbsolutePath())) {
|
|
||||||
file = new File(canonical).getAbsoluteFile();
|
|
||||||
}
|
|
||||||
} catch (Exception x) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file.getAbsolutePath().startsWith("/proc/self/")) {
|
|
||||||
parcelDescriptor.close();
|
|
||||||
return file.getAbsolutePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
String path = Os.readlink(file.getAbsolutePath());
|
|
||||||
parcelDescriptor.close();
|
|
||||||
|
|
||||||
if (new File(path).exists()) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void updateFile(String path) {
|
public static void updateFile(String path) {
|
||||||
DocumentFile file = parseFile(path);
|
DocumentFile file = parseFile(path);
|
||||||
Uri uri = file.getUri();
|
Uri uri = file.getUri();
|
||||||
|
@ -310,4 +278,12 @@ public class FileUtils {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getChild(String path, String name){
|
||||||
|
return parseFile(path).findFile(name).getUri().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDirectory(String path) {
|
||||||
|
return parseFile(path).isDirectory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,18 @@ import android.util.Log;
|
||||||
import com.panda3ds.pandroid.app.GameActivity;
|
import com.panda3ds.pandroid.app.GameActivity;
|
||||||
import com.panda3ds.pandroid.data.GsonConfigParser;
|
import com.panda3ds.pandroid.data.GsonConfigParser;
|
||||||
import com.panda3ds.pandroid.data.game.GameMetadata;
|
import com.panda3ds.pandroid.data.game.GameMetadata;
|
||||||
|
import com.panda3ds.pandroid.data.game.GamesFolder;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class GameUtils {
|
public class GameUtils {
|
||||||
private static final Bitmap DEFAULT_ICON = Bitmap.createBitmap(48, 48, Bitmap.Config.ARGB_8888);
|
private static final Bitmap DEFAULT_ICON = Bitmap.createBitmap(48, 48, Bitmap.Config.ARGB_8888);
|
||||||
public static GsonConfigParser parser = new GsonConfigParser(Constants.PREF_GAME_UTILS);
|
private final static GsonConfigParser parser = new GsonConfigParser(Constants.PREF_GAME_UTILS);
|
||||||
|
|
||||||
private static DataModel data;
|
private static DataModel data;
|
||||||
|
|
||||||
|
@ -27,10 +29,12 @@ public class GameUtils {
|
||||||
|
|
||||||
public static void initialize() {
|
public static void initialize() {
|
||||||
data = parser.load(DataModel.class);
|
data = parser.load(DataModel.class);
|
||||||
|
refreshFolders();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameMetadata findByRomPath(String romPath) {
|
public static GameMetadata findByRomPath(String romPath) {
|
||||||
for (GameMetadata game : data.games) {
|
ArrayList<GameMetadata> games = getGames();
|
||||||
|
for (GameMetadata game : games) {
|
||||||
if (Objects.equals(romPath, game.getRealPath())) {
|
if (Objects.equals(romPath, game.getRealPath())) {
|
||||||
return game;
|
return game;
|
||||||
}
|
}
|
||||||
|
@ -42,9 +46,7 @@ public class GameUtils {
|
||||||
currentGame = game;
|
currentGame = game;
|
||||||
String path = game.getRealPath();
|
String path = game.getRealPath();
|
||||||
if (path.contains("://")) {
|
if (path.contains("://")) {
|
||||||
String[] parts = Uri.decode(game.getRomPath()).split("/");
|
path = "game://internal/" + FileUtils.getName(game.getRealPath());
|
||||||
String name = parts[parts.length - 1];
|
|
||||||
path = "game://internal/" + name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context.startActivity(new Intent(context, GameActivity.class).putExtra(Constants.ACTIVITY_PARAMETER_PATH, path));
|
context.startActivity(new Intent(context, GameActivity.class).putExtra(Constants.ACTIVITY_PARAMETER_PATH, path));
|
||||||
|
@ -72,19 +74,39 @@ public class GameUtils {
|
||||||
|
|
||||||
Uri uri = Uri.parse(path);
|
Uri uri = Uri.parse(path);
|
||||||
switch (uri.getScheme().toLowerCase()) {
|
switch (uri.getScheme().toLowerCase()) {
|
||||||
|
case "folder": {
|
||||||
|
return FileUtils.getChild(data.folders.get(uri.getAuthority()).getPath(), uri.getPathSegments().get(0));
|
||||||
|
}
|
||||||
case "elf": {
|
case "elf": {
|
||||||
return FileUtils.getResourcePath(Constants.RESOURCE_FOLDER_ELF) + "/" + uri.getAuthority();
|
return FileUtils.getResourcePath(Constants.RESOURCE_FOLDER_ELF) + "/" + uri.getAuthority();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<GameMetadata> getGames() {
|
public static void refreshFolders() {
|
||||||
return new ArrayList<>(data.games);
|
String[] keys = data.folders.keySet().toArray(new String[0]);
|
||||||
|
for (String key : keys) {
|
||||||
|
GamesFolder folder = data.folders.get(key);
|
||||||
|
if (!folder.isValid()){
|
||||||
|
data.folders.remove(key);
|
||||||
|
} else {
|
||||||
|
folder.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeChanges() {
|
public static ArrayList<GameMetadata> getGames() {
|
||||||
|
ArrayList<GameMetadata> games = new ArrayList<>();
|
||||||
|
games.addAll(data.games);
|
||||||
|
for (GamesFolder folder: data.folders.values()){
|
||||||
|
games.addAll(folder.getGames());
|
||||||
|
}
|
||||||
|
return games;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeChanges() {
|
||||||
parser.save(data);
|
parser.save(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +139,35 @@ public class GameUtils {
|
||||||
return DEFAULT_ICON;
|
return DEFAULT_ICON;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GamesFolder[] getFolders() {
|
||||||
|
return data.folders.values().toArray(new GamesFolder[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerFolder(String path) {
|
||||||
|
if (!data.folders.containsKey(path)){
|
||||||
|
GamesFolder folder = new GamesFolder(path);
|
||||||
|
data.folders.put(folder.getId(),folder);
|
||||||
|
folder.refresh();
|
||||||
|
writeChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeFolder(GamesFolder folder) {
|
||||||
|
data.folders.remove(folder.getId());
|
||||||
|
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<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,27 @@ package com.panda3ds.pandroid.view;
|
||||||
|
|
||||||
import static android.opengl.GLES32.*;
|
import static android.opengl.GLES32.*;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.opengl.GLSurfaceView;
|
import android.opengl.GLSurfaceView;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import com.panda3ds.pandroid.AlberDriver;
|
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.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;
|
||||||
|
import com.panda3ds.pandroid.view.ds.DsLayoutManager;
|
||||||
import com.panda3ds.pandroid.view.renderer.ConsoleRenderer;
|
import com.panda3ds.pandroid.view.renderer.ConsoleRenderer;
|
||||||
import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout;
|
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.egl.EGLConfig;
|
||||||
import javax.microedition.khronos.opengles.GL10;
|
import javax.microedition.khronos.opengles.GL10;
|
||||||
|
|
||||||
|
@ -38,7 +41,7 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
|
||||||
|
|
||||||
screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
|
screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
|
||||||
screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
|
screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
|
||||||
setLayout(new DefaultScreenLayout());
|
setLayout(DsLayoutManager.createLayout(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -74,9 +77,9 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
|
||||||
glGenTextures(1, generateBuffer, 0);
|
glGenTextures(1, generateBuffer, 0);
|
||||||
screenTexture = generateBuffer[0];
|
screenTexture = generateBuffer[0];
|
||||||
glBindTexture(GL_TEXTURE_2D, screenTexture);
|
glBindTexture(GL_TEXTURE_2D, screenTexture);
|
||||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, screenWidth, screenHeight);
|
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, Constants.N3DS_WIDTH, Constants.N3DS_FULL_HEIGHT);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_LINEAR);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
glGenFramebuffers(1, generateBuffer, 0);
|
glGenFramebuffers(1, generateBuffer, 0);
|
||||||
|
@ -95,19 +98,17 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
|
||||||
if (!AlberDriver.LoadRom(romPath)) {
|
if (!AlberDriver.LoadRom(romPath)) {
|
||||||
// Get a handler that can be used to post to the main thread
|
// Get a handler that can be used to post to the main thread
|
||||||
Handler mainHandler = new Handler(context.getMainLooper());
|
Handler mainHandler = new Handler(context.getMainLooper());
|
||||||
|
mainHandler.post(()-> {
|
||||||
Runnable runnable = new Runnable() {
|
new BottomAlertDialog(context)
|
||||||
@Override
|
.setTitle(R.string.failed_load_rom)
|
||||||
public void run() {
|
.setMessage(R.string.dialog_message_invalid_rom)
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
.setPositiveButton(android.R.string.ok, (dialog, witch) -> {
|
||||||
builder.setTitle("Failed to load ROM")
|
dialog.dismiss();
|
||||||
.setMessage("Make sure it's a valid 3DS ROM and that storage permissions are configured properly.")
|
CompatUtils.findActivity(context).finishAndRemoveTask();
|
||||||
.setPositiveButton("OK", null)
|
})
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.show();
|
.show();
|
||||||
}
|
});
|
||||||
};
|
|
||||||
mainHandler.post(runnable);
|
|
||||||
|
|
||||||
GameMetadata game = GameUtils.getCurrentGame();
|
GameMetadata game = GameUtils.getCurrentGame();
|
||||||
GameUtils.removeGame(game);
|
GameUtils.removeGame(game);
|
||||||
|
@ -122,15 +123,16 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
|
||||||
SMDH smdh = new SMDH(smdhData);
|
SMDH smdh = new SMDH(smdhData);
|
||||||
Log.i(Constants.LOG_TAG, "Loaded rom SDMH");
|
Log.i(Constants.LOG_TAG, "Loaded rom SDMH");
|
||||||
Log.i(Constants.LOG_TAG, String.format("You are playing '%s' published by '%s'", smdh.getTitle(), smdh.getPublisher()));
|
Log.i(Constants.LOG_TAG, String.format("You are playing '%s' published by '%s'", smdh.getTitle(), smdh.getPublisher()));
|
||||||
GameMetadata game = GameUtils.getCurrentGame();
|
GameUtils.getCurrentGame().applySMDH(smdh);
|
||||||
GameUtils.removeGame(game);
|
|
||||||
GameUtils.addGame(GameMetadata.applySMDH(game, smdh));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PerformanceMonitor.initialize(getBackendName());
|
PerformanceMonitor.initialize(getBackendName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDrawFrame(GL10 unused) {
|
public void onDrawFrame(GL10 unused) {
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
if (AlberDriver.HasRomLoaded()) {
|
if (AlberDriver.HasRomLoaded()) {
|
||||||
AlberDriver.RunFrame(screenFbo);
|
AlberDriver.RunFrame(screenFbo);
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.panda3ds.pandroid.view.ds;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
|
||||||
|
class Bounds {
|
||||||
|
public int left = 0;
|
||||||
|
public int right = 0;
|
||||||
|
public int top = 0;
|
||||||
|
public int bottom = 0;
|
||||||
|
|
||||||
|
public void normalize(){
|
||||||
|
left = Math.abs(left);
|
||||||
|
right = Math.abs(right);
|
||||||
|
top = Math.abs(top);
|
||||||
|
bottom = Math.abs(bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyWithAspect(Rect rect, int width, double aspectRatio){
|
||||||
|
normalize();
|
||||||
|
rect.set(left, top, width-right, (int) Math.round((width-right-left)*aspectRatio)+top);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply(Rect rect, int width, int height){
|
||||||
|
normalize();
|
||||||
|
rect.set(left, top, width-right, height-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(int x, int y){
|
||||||
|
left += x;
|
||||||
|
right -= x;
|
||||||
|
|
||||||
|
top += y;
|
||||||
|
bottom -= y;
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fixOverlay(int width, int height, int size) {
|
||||||
|
if (left > (width-right) - size){
|
||||||
|
right = (width-left) - size;
|
||||||
|
}
|
||||||
|
if (top > (height - bottom) - size){
|
||||||
|
bottom = (height - top) - size;
|
||||||
|
}
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,353 @@
|
||||||
|
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.Vector2;
|
||||||
|
import com.panda3ds.pandroid.utils.CompatUtils;
|
||||||
|
import com.panda3ds.pandroid.utils.Constants;
|
||||||
|
|
||||||
|
@SuppressLint("ViewConstructor")
|
||||||
|
public class DsEditorView extends FrameLayout {
|
||||||
|
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 = CompatUtils.applyDimen(TypedValue.COMPLEX_UNIT_DIP, 1);
|
||||||
|
int colorBottomSelection = CompatUtils.resolveColor(context, androidx.appcompat.R.attr.colorPrimary);
|
||||||
|
int colorTopSelection = CompatUtils.resolveColor(context, com.google.android.material.R.attr.colorAccent);
|
||||||
|
|
||||||
|
selectionPaint.setColor(colorTopSelection);
|
||||||
|
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<String> 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;
|
||||||
|
refreshPoints();
|
||||||
|
});
|
||||||
|
|
||||||
|
spacePoint = new PointView();
|
||||||
|
spacePoint.setColor(CompatUtils.resolveColor(context, com.google.android.material.R.attr.colorOnPrimary), colorTopSelection);
|
||||||
|
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().onlyTop = !layout.getCurrentModel().onlyTop;
|
||||||
|
refreshPoints();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
topDisplay = new PointView();
|
||||||
|
topDisplay.setText(R.string.top_display);
|
||||||
|
topDisplay.setOnTouchListener(new DisplayTouchEvent(true));
|
||||||
|
topDisplay.setTextColor(colorTopSelection);
|
||||||
|
topDisplay.setBackground(new SelectionDrawable(colorTopSelection));
|
||||||
|
|
||||||
|
bottomDisplay = new PointView();
|
||||||
|
bottomDisplay.setText(R.string.bottom_display);
|
||||||
|
bottomDisplay.setOnTouchListener(new DisplayTouchEvent(false));
|
||||||
|
bottomDisplay.setTextColor(colorBottomSelection);
|
||||||
|
bottomDisplay.setBackground(new SelectionDrawable(colorBottomSelection));
|
||||||
|
|
||||||
|
topDisplayResizer = new PointView();
|
||||||
|
topDisplayResizer.setColor(0, colorTopSelection);
|
||||||
|
topDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(true));
|
||||||
|
|
||||||
|
bottomDisplayResizer = new PointView();
|
||||||
|
bottomDisplayResizer.setColor(0, colorBottomSelection);
|
||||||
|
bottomDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
super.draw(canvas);
|
||||||
|
if (this.width != getWidth() || this.height != getHeight()) {
|
||||||
|
this.width = getWidth();
|
||||||
|
this.height = getHeight();
|
||||||
|
refreshLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshPoints() {
|
||||||
|
Model data = layout.getCurrentModel();
|
||||||
|
data.preferredTop.fixOverlay(width, height, (int) (SIZE_DP * 5));
|
||||||
|
data.preferredBottom.fixOverlay(width, height, (int) (SIZE_DP * 30));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Vector2 downEvent = null;
|
||||||
|
|
||||||
|
private DisplayTouchEvent(boolean topScreen) {
|
||||||
|
this.topScreen = topScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
Bounds preferred = topScreen ? layout.getCurrentModel().preferredTop : layout.getCurrentModel().preferredBottom;
|
||||||
|
if (layout.getCurrentModel().mode == Mode.ABSOLUTE && event.getAction() != MotionEvent.ACTION_UP) {
|
||||||
|
if (downEvent == null) {
|
||||||
|
downEvent = new Vector2(event.getRawX(), event.getRawY());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
preferred.move((int) (event.getRawX() - downEvent.x), (int) (event.getRawY() - downEvent.y));
|
||||||
|
downEvent.set(event.getRawX(), event.getRawY());
|
||||||
|
refreshPoints();
|
||||||
|
return true;
|
||||||
|
} else if (layout.getCurrentModel().mode == Mode.SINGLE && event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
callOnClick();
|
||||||
|
}
|
||||||
|
downEvent = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DisplayResizeTouchEvent implements OnTouchListener {
|
||||||
|
private final boolean topScreen;
|
||||||
|
|
||||||
|
private DisplayResizeTouchEvent(boolean topScreen) {
|
||||||
|
this.topScreen = topScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
Bounds preferred = topScreen ? layout.getCurrentModel().preferredTop : layout.getCurrentModel().preferredBottom;
|
||||||
|
if (event.getAction() != MotionEvent.ACTION_UP) {
|
||||||
|
preferred.right = (int) (width - (((PointView) v).x() + event.getX()));
|
||||||
|
preferred.bottom = (int) (height - (((PointView) v).y() + event.getY()));
|
||||||
|
refreshPoints();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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(65, Color.red(color), Color.green(color), Color.blue(color)));
|
||||||
|
canvas.drawRect(this.getBounds(), solidPaint);
|
||||||
|
canvas.drawRect(this.getBounds(), selectionPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
package com.panda3ds.pandroid.view.ds;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.view.Gravity;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (data.lockAspect) {
|
||||||
|
data.preferredTop.applyWithAspect(topDisplay, (int) screenSize.x, (double) sourceTop.y / sourceTop.x);
|
||||||
|
data.preferredBottom.applyWithAspect(bottomDisplay, (int) screenSize.x, (double) sourceBottom.y / sourceBottom.x);
|
||||||
|
} else {
|
||||||
|
data.preferredTop.apply(topDisplay, (int) screenSize.x, (int) screenSize.y);
|
||||||
|
data.preferredBottom.apply(bottomDisplay, (int) screenSize.x, (int) screenSize.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void single(Model data) {
|
||||||
|
Vector2 source = data.onlyTop ? sourceTop : sourceBottom;
|
||||||
|
Rect dest = data.onlyTop ? 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.onlyTop ? 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.onlyTop = false;
|
||||||
|
|
||||||
|
Model model3 = new Model();
|
||||||
|
model3.mode = Mode.SINGLE;
|
||||||
|
model3.onlyTop = 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<Model[]> models = new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.panda3ds.pandroid.view.ds;
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
SINGLE,
|
||||||
|
RELATIVE,
|
||||||
|
ABSOLUTE
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.panda3ds.pandroid.view.ds;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Gravity;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.utils.Constants;
|
||||||
|
|
||||||
|
class Model implements Cloneable {
|
||||||
|
public Mode mode = Mode.RELATIVE;
|
||||||
|
public final Bounds preferredTop = new Bounds();
|
||||||
|
public final Bounds preferredBottom = new Bounds();
|
||||||
|
public boolean reverse = false;
|
||||||
|
public boolean onlyTop = 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,12 +8,20 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.panda3ds.pandroid.R;
|
import com.panda3ds.pandroid.R;
|
||||||
import com.panda3ds.pandroid.data.game.GameMetadata;
|
import com.panda3ds.pandroid.data.game.GameMetadata;
|
||||||
|
import com.panda3ds.pandroid.lang.Function;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
class GameAdapter extends RecyclerView.Adapter<ItemHolder> {
|
class GameAdapter extends RecyclerView.Adapter<ItemHolder> {
|
||||||
private final ArrayList<GameMetadata> games = new ArrayList<>();
|
private final ArrayList<GameMetadata> games = new ArrayList<>();
|
||||||
|
private final Function<GameMetadata> clickListener;
|
||||||
|
private final Function<GameMetadata> longClickListener;
|
||||||
|
|
||||||
|
GameAdapter(Function<GameMetadata> clickListener, Function<GameMetadata> longClickListener) {
|
||||||
|
this.clickListener = clickListener;
|
||||||
|
this.longClickListener = longClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
@ -23,6 +31,11 @@ class GameAdapter extends RecyclerView.Adapter<ItemHolder> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ItemHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ItemHolder holder, int position) {
|
||||||
|
holder.itemView.setOnClickListener(v -> clickListener.run(games.get(position)));
|
||||||
|
holder.itemView.setOnLongClickListener(v -> {
|
||||||
|
longClickListener.run(games.get(position));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
holder.apply(games.get(position));
|
holder.apply(games.get(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,15 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.panda3ds.pandroid.data.game.GameMetadata;
|
import com.panda3ds.pandroid.data.game.GameMetadata;
|
||||||
|
import com.panda3ds.pandroid.lang.Function;
|
||||||
|
import com.panda3ds.pandroid.utils.GameUtils;
|
||||||
import com.panda3ds.pandroid.view.recycler.AutoFitGridLayout;
|
import com.panda3ds.pandroid.view.recycler.AutoFitGridLayout;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GamesGridView extends RecyclerView {
|
public class GamesGridView extends RecyclerView {
|
||||||
private final GameAdapter adapter;
|
private final GameAdapter adapter;
|
||||||
|
private Function<GameMetadata> longClickListener = null;
|
||||||
|
|
||||||
public GamesGridView(@NonNull Context context) {
|
public GamesGridView(@NonNull Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
@ -26,7 +29,21 @@ public class GamesGridView extends RecyclerView {
|
||||||
public GamesGridView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
public GamesGridView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
setLayoutManager(new AutoFitGridLayout(getContext(), 170));
|
setLayoutManager(new AutoFitGridLayout(getContext(), 170));
|
||||||
setAdapter(adapter = new GameAdapter());
|
setAdapter(adapter = new GameAdapter(this::onClickGame, this::onLongClickGame));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItemLongClick(Function<GameMetadata> longClickListener) {
|
||||||
|
this.longClickListener = longClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClickGame(GameMetadata game) {
|
||||||
|
GameUtils.launch(getContext(), game);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLongClickGame(GameMetadata game) {
|
||||||
|
if (longClickListener != null){
|
||||||
|
longClickListener.run(game);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGameList(List<GameMetadata> games) {
|
public void setGameList(List<GameMetadata> games) {
|
||||||
|
|
|
@ -5,10 +5,8 @@ import android.view.View;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.AppCompatTextView;
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.panda3ds.pandroid.R;
|
import com.panda3ds.pandroid.R;
|
||||||
import com.panda3ds.pandroid.data.game.GameMetadata;
|
import com.panda3ds.pandroid.data.game.GameMetadata;
|
||||||
import com.panda3ds.pandroid.utils.GameUtils;
|
|
||||||
|
|
||||||
class ItemHolder extends RecyclerView.ViewHolder {
|
class ItemHolder extends RecyclerView.ViewHolder {
|
||||||
public ItemHolder(@NonNull View itemView) {
|
public ItemHolder(@NonNull View itemView) {
|
||||||
|
@ -22,9 +20,5 @@ class ItemHolder extends RecyclerView.ViewHolder {
|
||||||
.setImageBitmap(game.getIcon());
|
.setImageBitmap(game.getIcon());
|
||||||
((AppCompatTextView) itemView.findViewById(R.id.description))
|
((AppCompatTextView) itemView.findViewById(R.id.description))
|
||||||
.setText(game.getPublisher());
|
.setText(game.getPublisher());
|
||||||
|
|
||||||
itemView.setOnClickListener((v) -> {
|
|
||||||
GameUtils.launch(v.getContext(), game);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,11 @@
|
||||||
package com.panda3ds.pandroid.view.renderer.layout;
|
package com.panda3ds.pandroid.view.renderer.layout;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.view.Gravity;
|
||||||
|
|
||||||
import com.panda3ds.pandroid.math.Vector2;
|
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 topDisplay = new Rect();
|
||||||
private final Rect bottomDisplay = 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 topSourceSize = new Vector2(1.0f, 1.0f);
|
||||||
private final Vector2 bottomSourceSize = 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
|
@Override
|
||||||
public void update(int screenWidth, int screenHeight) {
|
public void update(int screenWidth, int screenHeight) {
|
||||||
screenSize.set(screenWidth, screenHeight);
|
screenSize.set(screenWidth, screenHeight);
|
||||||
|
@ -29,16 +36,50 @@ public class DefaultScreenLayout implements ConsoleLayout {
|
||||||
updateBounds();
|
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() {
|
private void updateBounds() {
|
||||||
int screenWidth = (int) screenSize.x;
|
int screenWidth = (int) screenSize.x;
|
||||||
int screenHeight = (int) screenSize.y;
|
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) {
|
if (screenWidth > screenHeight) {
|
||||||
int topDisplayWidth = (int) ((screenHeight / topSourceSize.y) * topSourceSize.x);
|
int topDisplayWidth = (int) ((screenHeight / topSourceSize.y) * topSourceSize.x);
|
||||||
int topDisplayHeight = screenHeight;
|
int topDisplayHeight = screenHeight;
|
||||||
|
|
||||||
if (topDisplayWidth > (screenWidth * 0.7)) {
|
if (topDisplayWidth > (screenWidth * landscapeSpace)) {
|
||||||
topDisplayWidth = (int) (screenWidth * 0.7);
|
topDisplayWidth = (int) (screenWidth * landscapeSpace);
|
||||||
topDisplayHeight = (int) ((topDisplayWidth / topSourceSize.x) * topSourceSize.y);
|
topDisplayHeight = (int) ((topDisplayWidth / topSourceSize.x) * topSourceSize.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +87,7 @@ public class DefaultScreenLayout 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);
|
||||||
|
adjustHorizontalGravity();
|
||||||
} else {
|
} else {
|
||||||
int topScreenHeight = (int) ((screenWidth / topSourceSize.x) * topSourceSize.y);
|
int topScreenHeight = (int) ((screenWidth / topSourceSize.x) * topSourceSize.y);
|
||||||
topDisplay.set(0, 0, screenWidth, topScreenHeight);
|
topDisplay.set(0, 0, screenWidth, topScreenHeight);
|
||||||
|
@ -62,9 +104,42 @@ public class DefaultScreenLayout implements ConsoleLayout {
|
||||||
|
|
||||||
topDisplay.set(0, 0, screenWidth, topScreenHeight);
|
topDisplay.set(0, 0, screenWidth, topScreenHeight);
|
||||||
bottomDisplay.set(bottomDisplayX, topScreenHeight, bottomDisplayX + bottomDisplayWidth, topScreenHeight + bottomDisplayHeight);
|
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
|
@Override
|
||||||
public Rect getBottomDisplayBounds() {
|
public Rect getBottomDisplayBounds() {
|
||||||
return bottomDisplay;
|
return bottomDisplay;
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
<item>
|
<item>
|
||||||
<shape>
|
<shape>
|
||||||
<corners android:radius="20dp"/>
|
<corners android:radius="20dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="?colorSurfaceVariant"/>
|
||||||
<solid android:color="?colorSurface"/>
|
<solid android:color="?colorSurface"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
<solid android:color="?colorSurfaceVariant"/>
|
||||||
|
</shape>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:pathData="M8,19h3v4h2v-4h3l-4,-4 -4,4zM16,5h-3L13,1h-2v4L8,5l4,4 4,-4zM4,11v2h16v-2L4,11z"/>
|
||||||
|
</vector>
|
10
src/pandroid/app/src/main/res/drawable/ic_arrow_down.xml
Normal file
10
src/pandroid/app/src/main/res/drawable/ic_arrow_down.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?colorOnSurface">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:pathData="M7.41,8.59 L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
|
||||||
|
</vector>
|
10
src/pandroid/app/src/main/res/drawable/ic_arrow_up.xml
Normal file
10
src/pandroid/app/src/main/res/drawable/ic_arrow_up.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?colorOnSurface">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:pathData="M7.41,15.41 L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
||||||
|
</vector>
|
|
@ -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"/>
|
||||||
|
|
10
src/pandroid/app/src/main/res/drawable/ic_compare_arrow.xml
Normal file
10
src/pandroid/app/src/main/res/drawable/ic_compare_arrow.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?colorOnSurface">
|
||||||
|
<path
|
||||||
|
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"/>
|
||||||
|
</vector>
|
|
@ -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"/>
|
||||||
|
|
|
@ -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="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
<path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
10
src/pandroid/app/src/main/res/drawable/ic_folder.xml
Normal file
10
src/pandroid/app/src/main/res/drawable/ic_folder.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?colorOnSurface">
|
||||||
|
<path
|
||||||
|
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"/>
|
||||||
|
</vector>
|
|
@ -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>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v14c0,1.11 0.89,2 2,2h18c1.11,0 2,-0.89 2,-2L23,5c0,-1.11 -0.89,-2 -2,-2zM21,19.02L3,19.02L3,4.98h18v14.04zM10,12L8,12l4,-4 4,4h-2v4h-4v-4z"/>
|
||||||
|
</vector>
|
|
@ -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>
|
|
|
@ -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>
|
|
39
src/pandroid/app/src/main/res/layout/dialog_bottom_sheet.xml
Normal file
39
src/pandroid/app/src/main/res/layout/dialog_bottom_sheet.xml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
app:strokeWidth="1dp"
|
||||||
|
app:cardCornerRadius="24dp"
|
||||||
|
app:strokeColor="?colorSurfaceVariant"
|
||||||
|
android:background="@drawable/alert_dialog_background">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="?colorSurface">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="128dp"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
android:background="@drawable/medium_card_background"
|
||||||
|
android:backgroundTint="?colorSurfaceVariant"/>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
129
src/pandroid/app/src/main/res/layout/dialog_game_about.xml
Normal file
129
src/pandroid/app/src/main/res/layout/dialog_game_about.xml
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="top|center"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="128dp"
|
||||||
|
android:layout_height="128dp"
|
||||||
|
app:cardCornerRadius="18dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
app:strokeWidth="0dp">
|
||||||
|
<com.panda3ds.pandroid.view.gamesgrid.GameIconView
|
||||||
|
android:id="@+id/game_icon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?colorSurfaceVariant"/>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="30dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/game_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/game_publisher"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:alpha="0.7"/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/region"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="20dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/region"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:alpha="0.76"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/directory"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="20dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/directory"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:alpha="0.76"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:layout_marginVertical="10dp"
|
||||||
|
android:gravity="end|center">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/shortcut"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:icon="@drawable/ic_shortcut"
|
||||||
|
app:iconTint="?colorOnSurfaceVariant"
|
||||||
|
android:textColor="?colorOnSurfaceVariant"
|
||||||
|
android:backgroundTint="?colorSurfaceVariant"
|
||||||
|
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
|
||||||
|
android:id="@+id/play"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/play"
|
||||||
|
android:textColor="?colorOnPrimary"
|
||||||
|
android:backgroundTint="?colorPrimary"
|
||||||
|
app:icon="@drawable/ic_play"
|
||||||
|
app:rippleColor="?colorOnPrimary"
|
||||||
|
app:iconTint="?colorOnPrimary"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
99
src/pandroid/app/src/main/res/layout/dialog_games_folder.xml
Normal file
99
src/pandroid/app/src/main/res/layout/dialog_games_folder.xml
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center|top"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:src="@drawable/ic_folder"
|
||||||
|
android:tint="?colorOnSurface"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:paddingVertical="5dp"
|
||||||
|
android:textSize="20sp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:paddingVertical="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/directory"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/directory"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:alpha="0.76"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/games"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="20dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/games"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:alpha="0.76"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="end|center">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/remove"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/remove"
|
||||||
|
app:icon="@drawable/ic_delete"
|
||||||
|
android:textColor="#FFF"
|
||||||
|
app:iconTint="#FFF"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
app:backgroundTint="#CC0000"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/ok"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:text="@android:string/ok"
|
||||||
|
app:icon="@drawable/ic_done"
|
||||||
|
android:textColor="?colorOnSurfaceVariant"
|
||||||
|
app:iconTint="?colorOnSurfaceVariant"
|
||||||
|
app:backgroundTint="?colorSurfaceVariant"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
15
src/pandroid/app/src/main/res/layout/dialog_select_theme.xml
Normal file
15
src/pandroid/app/src/main/res/layout/dialog_select_theme.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/theme_selector"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingHorizontal="20dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="4dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/up"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_arrow_up"
|
||||||
|
android:background="@drawable/simple_card_background"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:backgroundTint="?colorPrimary"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
android:tint="?colorOnPrimary"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_align_center"
|
||||||
|
android:background="@drawable/simple_card_background"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:backgroundTint="?colorPrimary"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
android:tint="?colorOnPrimary"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/down"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_arrow_down"
|
||||||
|
android:background="@drawable/simple_card_background"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:backgroundTint="?colorPrimary"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
android:tint="?colorOnPrimary"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/revert"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_compare_arrow"
|
||||||
|
android:background="@drawable/simple_card_background"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:backgroundTint="?colorPrimary"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
android:tint="?colorOnPrimary"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:focusable="true"
|
||||||
|
android:clickable="true">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingVertical="4dp"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:background="@drawable/simple_card_background"
|
||||||
|
android:backgroundTint="?colorSurfaceVariant"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:elevation="4dp">
|
||||||
|
|
||||||
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
android:id="@+id/checkbox"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:padding="0dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:text="@string/fix_aspect"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
30
src/pandroid/app/src/main/res/layout/ds_editor_spinner.xml
Normal file
30
src/pandroid/app/src/main/res/layout/ds_editor_spinner.xml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="4dp">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/simple_card_background"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:backgroundTint="?colorSurfaceVariant">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatSpinner
|
||||||
|
android:id="@+id/spinner"
|
||||||
|
android:layout_width="140dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_margin="6dp"
|
||||||
|
android:dropDownWidth="140dp"
|
||||||
|
android:popupElevation="2dp"
|
||||||
|
android:popupBackground="@drawable/ds_editor_popup_background"
|
||||||
|
android:backgroundTint="?colorOnSurfaceVariant"
|
||||||
|
android:foregroundTint="?colorOnSurfaceVariant"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="128dp"
|
||||||
|
android:layout_height="35dp"
|
||||||
|
android:paddingHorizontal="6dp"
|
||||||
|
android:textColor="?colorOnSurfaceVariant"
|
||||||
|
android:background="?colorSurfaceVariant"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:gravity="start|center">
|
||||||
|
</TextView>
|
|
@ -69,16 +69,12 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Drawer content layout -->
|
<!-- Drawer content layout -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="start"
|
android:gravity="start">
|
||||||
android:layout_marginTop="195dp">
|
|
||||||
|
|
||||||
<com.google.android.material.navigation.NavigationView
|
<com.google.android.material.navigation.NavigationView
|
||||||
android:id="@+id/menu"
|
android:id="@+id/menu"
|
||||||
|
@ -91,4 +87,7 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
</androidx.drawerlayout.widget.DrawerLayout>
|
</androidx.drawerlayout.widget.DrawerLayout>
|
||||||
|
|
34
src/pandroid/app/src/main/res/layout/hold_theme_preview.xml
Normal file
34
src/pandroid/app/src/main/res/layout/hold_theme_preview.xml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:background="?colorSurface">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:background="@drawable/simple_card_background"
|
||||||
|
android:backgroundTint="?colorSurfaceVariant">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
android:background="@drawable/medium_card_background"
|
||||||
|
android:backgroundTint="?colorOnSurfaceVariant"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center|top"
|
||||||
|
android:background="@drawable/medium_card_background"
|
||||||
|
android:backgroundTint="?colorPrimary"/>
|
||||||
|
|
||||||
|
|
||||||
|
</FrameLayout>
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:foreground="@drawable/rounded_selectable_item_background"
|
||||||
|
android:gravity="top|center">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/preview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:strokeColor="?colorSurfaceVariant"
|
||||||
|
android:layout_height="125dp">
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:text="@string/app_name"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/checkbox"
|
||||||
|
android:layout_width="26dp"
|
||||||
|
android:layout_height="26dp"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:checked="false"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -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"/>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/switchWidget"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@null"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:scaleX="0.75"
|
||||||
|
android:scaleY="0.75"/>
|
|
@ -10,6 +10,10 @@
|
||||||
android:id="@+id/resume"
|
android:id="@+id/resume"
|
||||||
android:icon="@drawable/ic_shortcut"
|
android:icon="@drawable/ic_shortcut"
|
||||||
android:title="@string/resume" />
|
android:title="@string/resume" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/ds_switch"
|
||||||
|
android:icon="@drawable/ic_switch_screen"
|
||||||
|
android:title="@string/swap_screen"/>
|
||||||
<item
|
<item
|
||||||
android:id="@+id/exit"
|
android:id="@+id/exit"
|
||||||
android:icon="@drawable/ic_exit"
|
android:icon="@drawable/ic_exit"
|
||||||
|
|
|
@ -53,7 +53,41 @@
|
||||||
<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>
|
||||||
|
<string name="apply">Aplicar</string>
|
||||||
|
<string name="pref_theme_summary">Definir o tema do aplicativo</string>
|
||||||
|
<string name="pref_theme_title">Tema do aplicativo</string>
|
||||||
|
<string name="failed_load_rom">Falha ao carregar a ROM.</string>
|
||||||
|
<string name="dialog_message_invalid_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.</string>
|
||||||
|
<string name="system">Sistema</string>
|
||||||
|
<string name="general">Geral</string>
|
||||||
|
<string name="pref_general_summary">Configurações gerais do emulador.</string>
|
||||||
|
<string name="bottom_display">Tela inferior</string>
|
||||||
|
<string name="top_display">Tela superior</string>
|
||||||
|
<string name="fix_aspect">Manter porporção</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="click_to_change">Clique para mudar</string>
|
||||||
|
<string name="swap_screen">Mudar telas</string>
|
||||||
|
<string name="pref_game_folders_summary">Pastas usadas para importar os jogos</string>
|
||||||
|
<string name="pref_game_folders">Pastas de jogos</string>
|
||||||
|
<string name="import_folder">Adicionar pasta</string>
|
||||||
|
<string name="games_count_f">%d Jogos</string>
|
||||||
|
<string name="directory">Diretorio</string>
|
||||||
|
<string name="remove">Remover</string>
|
||||||
|
<string name="play">Jogar</string>
|
||||||
|
<string name="region">Região</string>
|
||||||
|
<string name="region_north_armerican">Estados Unidos</string>
|
||||||
|
<string name="region_japan">Japão</string>
|
||||||
|
<string name="region_europe">Europa</string>
|
||||||
|
<string name="region_australia">Australia</string>
|
||||||
|
<string name="region_korean">Coréia</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>
|
||||||
|
|
|
@ -54,10 +54,46 @@
|
||||||
<string name="pref_advanced_summary">Logger, performance statistics, etc.</string>
|
<string name="pref_advanced_summary">Logger, performance statistics, etc.</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_logger_service_title">Logger</string>
|
<string name="pref_picture_in_picture_title">Picture In Picture</string>
|
||||||
<string name="pref_logger_service_summary">Store application logs to file.</string>
|
<string name="pref_picture_in_picture_summary">Minimize window when placed in the background.</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="graphics">Graphics</string>
|
<string name="tools">Tools</string>
|
||||||
|
<string name="pref_logger_service_title">Logger</string>
|
||||||
|
<string name="pref_logger_service_summary">Store application logs to file.</string>
|
||||||
<string name="loading">Loading</string>
|
<string name="loading">Loading</string>
|
||||||
|
<string name="apply">Apply</string>
|
||||||
|
<string name="pref_theme_summary">Set application theme</string>
|
||||||
|
<string name="pref_theme_title">Application theme</string>
|
||||||
|
<string name="failed_load_rom">Failed to load ROM</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="general">General</string>
|
||||||
|
<string name="pref_general_summary">General application configuration.</string>
|
||||||
|
<string name="dual_screen_layouts">Screen layouts</string>
|
||||||
|
<string name="dual_screen_layouts_summary">Change layout of console screens.</string>
|
||||||
|
<string name="click_to_change">Click to change</string>
|
||||||
|
<string name="swap_screen">Swap screen</string>
|
||||||
|
<string name="pref_game_folders_summary">Folders for importing games</string>
|
||||||
|
<string name="pref_game_folders">Game folders</string>
|
||||||
|
<string name="import_folder">Import folder</string>
|
||||||
|
<string name="games_count_f">%d Games</string>
|
||||||
|
<string name="directory">Directory</string>
|
||||||
|
<string name="remove">Remove</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_north_armerican">North American</string>
|
||||||
|
<string name="region_japan">Japan</string>
|
||||||
|
<string name="region_europe">Europe</string>
|
||||||
|
<string name="region_australia">Australia</string>
|
||||||
|
<string name="region_korean">Korean</string>
|
||||||
|
<string name="region_taiwan">Taiwan</string>
|
||||||
|
<string name="behavior">Behavior</string>
|
||||||
|
<string name="invalid_game">Invalid game</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -7,30 +7,21 @@
|
||||||
<item name="preferenceTheme">@style/PreferenceTheme</item>
|
<item name="preferenceTheme">@style/PreferenceTheme</item>
|
||||||
<item name="android:statusBarColor">?colorSurface</item>
|
<item name="android:statusBarColor">?colorSurface</item>
|
||||||
<item name="android:windowLightStatusBar">?isLightTheme</item>
|
<item name="android:windowLightStatusBar">?isLightTheme</item>
|
||||||
|
|
||||||
<item name="switchStyle">@style/SwitchStyle</item>
|
|
||||||
<item name="materialSwitchStyle">@style/SwitchStyle</item>
|
|
||||||
<item name="android:switchStyle">@style/SwitchStyle</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="SwitchStyle" parent="Widget.Material3.CompoundButton.MaterialSwitch">
|
|
||||||
<item name="android:thumb">@drawable/switch_thumb</item>
|
|
||||||
<item name="android:track">@drawable/switch_track</item>
|
|
||||||
<item name="thumbRadius">0dp</item>
|
|
||||||
<item name="android:thumbOffset">0dp</item>
|
|
||||||
<item name="android:padding">0dp</item>
|
|
||||||
<item name="showText">false</item>
|
|
||||||
<item name="android:showText">false</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="PreferenceTheme" parent="PreferenceThemeOverlay">
|
<style name="PreferenceTheme" parent="PreferenceThemeOverlay">
|
||||||
<item name="preferenceFragmentCompatStyle">@style/PreferenceStyle</item>
|
<item name="preferenceFragmentCompatStyle">@style/PreferenceStyle</item>
|
||||||
|
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="PreferenceStyle" parent="PreferenceFragment.Material">
|
<style name="PreferenceStyle" parent="PreferenceFragment.Material">
|
||||||
<item name="android:divider">?colorSurfaceVariant</item>
|
<item name="android:divider">?colorSurfaceVariant</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Preference.SwitchPreferenceCompat" parent="Preference.SwitchPreferenceCompat.Material">
|
||||||
|
<item name="android:widgetLayout">@layout/material_switch_widget</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="AlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
|
<style name="AlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
|
||||||
<item name="android:windowBackground">@drawable/alert_dialog_background</item>
|
<item name="android:windowBackground">@drawable/alert_dialog_background</item>
|
||||||
<item name="materialAlertDialogTitleTextStyle">@style/AlertDialog.Title</item>
|
<item name="materialAlertDialogTitleTextStyle">@style/AlertDialog.Title</item>
|
||||||
|
|
|
@ -2,29 +2,31 @@
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<SwitchPreference
|
<PreferenceCategory
|
||||||
|
app:title="@string/tools"
|
||||||
|
app:iconSpaceReserved="false">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
android:key="performanceMonitor"
|
android:key="performanceMonitor"
|
||||||
app:title="@string/pref_performance_monitor_title"
|
app:title="@string/pref_performance_monitor_title"
|
||||||
app:summary="@string/pref_performance_monitor_summary"
|
app:summary="@string/pref_performance_monitor_summary"
|
||||||
app:iconSpaceReserved="false"/>
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreferenceCompat
|
||||||
android:key="loggerService"
|
android:key="loggerService"
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
app:title="@string/pref_logger_service_title"
|
app:title="@string/pref_logger_service_title"
|
||||||
app:defaultValue="true"
|
|
||||||
android:summary="@string/pref_logger_service_summary"/>
|
android:summary="@string/pref_logger_service_summary"/>
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
app:title="@string/graphics">
|
app:title="@string/graphics">
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreferenceCompat
|
||||||
app:key="shaderJit"
|
app:key="shaderJit"
|
||||||
app:title="@string/pref_shader_jit_title"
|
app:title="@string/pref_shader_jit_title"
|
||||||
app:summary="@string/pref_shader_jit_summary"
|
app:summary="@string/pref_shader_jit_summary"
|
||||||
app:iconSpaceReserved="false"/>
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
|
@ -1,14 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
<com.panda3ds.pandroid.view.preferences.SingleSelectionPreferences
|
|
||||||
app:key="theme"
|
|
||||||
app:title="@string/theme"
|
|
||||||
app:iconSpaceReserved="false">
|
|
||||||
|
|
||||||
<Preference app:title="@string/theme_device"/>
|
|
||||||
<Preference app:title="@string/light"/>
|
|
||||||
<Preference app:title="@string/dark"/>
|
|
||||||
<Preference app:title="@string/black"/>
|
|
||||||
|
|
||||||
</com.panda3ds.pandroid.view.preferences.SingleSelectionPreferences>
|
|
||||||
</PreferenceScreen>
|
|
4
src/pandroid/app/src/main/res/xml/empty_preferences.xml
Normal file
4
src/pandroid/app/src/main/res/xml/empty_preferences.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
36
src/pandroid/app/src/main/res/xml/general_preference.xml
Normal file
36
src/pandroid/app/src/main/res/xml/general_preference.xml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<PreferenceCategory
|
||||||
|
app:title="@string/appearance"
|
||||||
|
app:iconSpaceReserved="false">
|
||||||
|
<Preference
|
||||||
|
android:key="appearance.theme"
|
||||||
|
app:title="@string/theme"
|
||||||
|
app:summary="@string/theme"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
<Preference
|
||||||
|
android:key="appearance.ds"
|
||||||
|
app:title="@string/dual_screen_layouts"
|
||||||
|
app:summary="@string/dual_screen_layouts_summary"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
</PreferenceCategory>
|
||||||
|
<PreferenceCategory
|
||||||
|
app:title="@string/games"
|
||||||
|
app:iconSpaceReserved="false">
|
||||||
|
<Preference
|
||||||
|
android:key="games.folders"
|
||||||
|
app:title="@string/pref_game_folders"
|
||||||
|
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"/>
|
||||||
|
</PreferenceCategory>
|
||||||
|
</PreferenceScreen>
|
|
@ -133,6 +133,12 @@
|
||||||
app:summary="none"
|
app:summary="none"
|
||||||
app:iconSpaceReserved="false"/>
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:title="@string/swap_screen"
|
||||||
|
app:key="CHANGE_DS_LAYOUT"
|
||||||
|
app:summary="none"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
|
@ -4,6 +4,7 @@
|
||||||
android:divider="#F00">
|
android:divider="#F00">
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
|
android:key="application"
|
||||||
app:title="@string/app_name"
|
app:title="@string/app_name"
|
||||||
app:enabled="false"
|
app:enabled="false"
|
||||||
app:summary="1.0"
|
app:summary="1.0"
|
||||||
|
@ -17,10 +18,10 @@
|
||||||
app:layout="@layout/preference_start_item"/>
|
app:layout="@layout/preference_start_item"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
app:key="appearance"
|
app:key="general"
|
||||||
app:icon="@drawable/ic_theme"
|
app:icon="@drawable/ic_settings"
|
||||||
app:title="@string/appearance"
|
app:title="@string/general"
|
||||||
app:summary="@string/pref_appearance_summary"
|
app:summary="@string/pref_general_summary"
|
||||||
app:layout="@layout/preference_start_item"/>
|
app:layout="@layout/preference_start_item"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
|
|
Loading…
Add table
Reference in a new issue