basic implement, ds editor, and remake theme selector

This commit is contained in:
gabriel 2024-02-24 12:02:19 -04:00
parent b0be9f5e60
commit dcda78de97
49 changed files with 1526 additions and 190 deletions

View file

@ -1,9 +1,11 @@
package com.panda3ds.pandroid.app;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.panda3ds.pandroid.R;
import com.google.android.material.color.DynamicColors;
import com.panda3ds.pandroid.data.config.GlobalConfig;
@ -28,5 +30,8 @@ public class BaseActivity extends AppCompatActivity {
private void applyTheme() {
currentTheme = PandroidApplication.getThemeId();
setTheme(currentTheme);
if (GlobalConfig.get(GlobalConfig.KEY_APP_THEME) == GlobalConfig.THEME_ANDROID){
DynamicColors.applyToActivityIfAvailable(this);
}
}
}

View file

@ -3,6 +3,7 @@ package com.panda3ds.pandroid.app;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@ -16,17 +17,22 @@ import com.panda3ds.pandroid.AlberDriver;
import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.game.AlberInputListener;
import com.panda3ds.pandroid.app.game.DrawerFragment;
import com.panda3ds.pandroid.app.game.EmulatorListener;
import com.panda3ds.pandroid.data.config.GlobalConfig;
import com.panda3ds.pandroid.input.InputHandler;
import com.panda3ds.pandroid.input.InputMap;
import com.panda3ds.pandroid.utils.Constants;
import com.panda3ds.pandroid.view.PandaGlSurfaceView;
import com.panda3ds.pandroid.view.PandaLayoutController;
import com.panda3ds.pandroid.view.ds.DsLayoutManager;
import com.panda3ds.pandroid.view.renderer.ConsoleRenderer;
import com.panda3ds.pandroid.view.utils.PerformanceView;
public class GameActivity extends BaseActivity {
public class GameActivity extends BaseActivity implements EmulatorListener {
private final DrawerFragment drawerFragment = new DrawerFragment();
private final AlberInputListener inputListener = new AlberInputListener(this::onBackPressed);
private final AlberInputListener inputListener = new AlberInputListener(this);
private ConsoleRenderer renderer;
private int currentDsLayout;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -43,6 +49,8 @@ public class GameActivity extends BaseActivity {
PandaGlSurfaceView pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.ACTIVITY_PARAMETER_PATH));
setContentView(R.layout.game_activity);
renderer = pandaSurface.getRenderer();
((FrameLayout) findViewById(R.id.panda_gl_frame))
.addView(pandaSurface, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
@ -104,6 +112,12 @@ public class GameActivity extends BaseActivity {
}
}
@Override
public void switchDualScreenLayout() {
currentDsLayout = currentDsLayout + 1 < DsLayoutManager.getLayoutCount() ? currentDsLayout + 1 : 0;
renderer.setLayout(DsLayoutManager.createLayout(currentDsLayout));
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent ev) {
if ((!drawerFragment.isOpened()) && InputHandler.processMotionEvent(ev)) {

View file

@ -2,6 +2,7 @@ package com.panda3ds.pandroid.app;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@ -16,15 +17,25 @@ public class MainActivity extends BaseActivity implements NavigationBarView.OnIt
private final GamesFragment gamesFragment = new GamesFragment();
private final SearchFragment searchFragment = new SearchFragment();
private final SettingsFragment settingsFragment = new SettingsFragment();
private NavigationBarView navigationBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavigationBarView bar = findViewById(R.id.navigation);
bar.setOnItemSelectedListener(this);
bar.postDelayed(() -> bar.setSelectedItemId(bar.getSelectedItemId()), 5);
navigationBar = findViewById(R.id.navigation);
navigationBar.setOnItemSelectedListener(this);
navigationBar.postDelayed(() -> navigationBar.setSelectedItemId(navigationBar.getSelectedItemId()), 5);
}
@Override
public void onBackPressed() {
if (navigationBar.getSelectedItemId() != R.id.games){
navigationBar.setSelectedItemId(R.id.games);
return;
}
super.onBackPressed();
}
@Override

View file

@ -7,7 +7,6 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;
import com.panda3ds.pandroid.lang.Function;

View file

@ -11,8 +11,8 @@ import java.util.Objects;
public class AlberInputListener implements Function<InputEvent> {
private final Runnable backListener;
public AlberInputListener(Runnable backListener) { this.backListener = backListener; }
private final EmulatorListener emulator;
public AlberInputListener(EmulatorListener emulator) { this.emulator = emulator; }
private final Vector2 axis = new Vector2(0.0f, 0.0f);
@ -21,7 +21,7 @@ public class AlberInputListener implements Function<InputEvent> {
KeyName key = InputMap.relative(event.getName());
if (Objects.equals(event.getName(), "KEYCODE_BACK")) {
backListener.run();
emulator.onBackPressed();
return;
}
@ -48,6 +48,11 @@ public class AlberInputListener implements Function<InputEvent> {
axis.x = event.getValue();
axisChanged = true;
break;
case CHANGE_DS_LAYOUT:
if (!event.isDown()){
emulator.switchDualScreenLayout();
}
break;
default:
if (event.isDown()) {
AlberDriver.KeyDown(key.getKeyId());

View file

@ -1,6 +1,7 @@
package com.panda3ds.pandroid.app.game;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
@ -42,13 +43,21 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe
super.onViewCreated(view, savedInstanceState);
drawerContainer.setVisibility(View.GONE);
GameMetadata game = GameUtils.getCurrentGame();
((NavigationView)view.findViewById(R.id.menu)).setNavigationItemSelectedListener(this);
refresh();
}
((GameIconView)view.findViewById(R.id.game_icon)).setImageBitmap(game.getIcon());
private void refresh(){
GameMetadata game = GameUtils.getCurrentGame();
View view = getView();
if (game.getIcon() != null && !game.getIcon().isRecycled()) {
((GameIconView) view.findViewById(R.id.game_icon)).setImageBitmap(game.getIcon());
} else {
((GameIconView) view.findViewById(R.id.game_icon)).setImageDrawable(new ColorDrawable(Color.TRANSPARENT));
}
((AppCompatTextView)view.findViewById(R.id.game_title)).setText(game.getTitle());
((AppCompatTextView)view.findViewById(R.id.game_publisher)).setText(game.getPublisher());
((NavigationView)view.findViewById(R.id.menu)).setNavigationItemSelectedListener(this);
}
@Override
@ -72,6 +81,7 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe
drawerContainer.setVisibility(View.VISIBLE);
drawerContainer.open();
drawerContainer.postDelayed(this::refreshLayout, 20);
refresh();
}
}

View file

@ -0,0 +1,7 @@
package com.panda3ds.pandroid.app.game;
public interface EmulatorListener {
void onBackPressed();
void switchDualScreenLayout();
}

View file

@ -1,13 +1,15 @@
package com.panda3ds.pandroid.app.main;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.PandroidApplication;
import com.panda3ds.pandroid.app.PreferenceActivity;
import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
import com.panda3ds.pandroid.app.preferences.AppearancePreferences;
import com.panda3ds.pandroid.app.preferences.GeneralPreferences;
import com.panda3ds.pandroid.app.preferences.AdvancedPreferences;
import com.panda3ds.pandroid.app.preferences.InputPreferences;
@ -15,8 +17,18 @@ public class SettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
setPreferencesFromResource(R.xml.start_preferences, rootKey);
findPreference("application").setSummary(getVersionName());
setItemClick("input", (item) -> PreferenceActivity.launch(requireContext(), InputPreferences.class));
setItemClick("appearance", (item)-> PreferenceActivity.launch(requireContext(), AppearancePreferences.class));
setItemClick("general", (item)-> PreferenceActivity.launch(requireContext(), GeneralPreferences.class));
setItemClick("advanced", (item)-> PreferenceActivity.launch(requireContext(), AdvancedPreferences.class));
}
private String getVersionName() {
try {
Context context = PandroidApplication.getAppContext();
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
} catch (Exception e){
return "???";
}
}
}

View file

@ -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;
});
}
}

View file

@ -0,0 +1,20 @@
package com.panda3ds.pandroid.app.preferences;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.PreferenceActivity;
import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
import com.panda3ds.pandroid.app.preferences.ds.DsListPreferences;
public class GeneralPreferences extends BasePreferenceFragment {
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
setPreferencesFromResource(R.xml.general_preference, rootKey);
setItemClick("appearance.theme", (pref) -> new ThemeSelectorDialog(requireActivity()).show());
setItemClick("appearance.ds", (pref) -> PreferenceActivity.launch(requireActivity(), DsListPreferences.class));
setActivityTitle(R.string.general);
}
}

View file

@ -0,0 +1,79 @@
package com.panda3ds.pandroid.app.preferences;
import android.app.Activity;
import android.content.Context;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatRadioButton;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.data.config.GlobalConfig;
import com.panda3ds.pandroid.view.recycler.AutoFitGridLayout;
import com.panda3ds.pandroid.view.recycler.SimpleListAdapter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Predicate;
public class ThemeSelectorDialog extends BottomSheetDialog {
private final SimpleListAdapter<Theme> adapter;
private final int currentTheme = GlobalConfig.get(GlobalConfig.KEY_APP_THEME);
private static final Theme[] themes = {
new Theme(R.style.Theme_Pandroid, R.string.theme_device, GlobalConfig.THEME_ANDROID),
new Theme(R.style.Theme_Pandroid_Light, R.string.light, GlobalConfig.THEME_LIGHT),
new Theme(R.style.Theme_Pandroid_Dark, R.string.dark, GlobalConfig.THEME_DARK),
new Theme(R.style.Theme_Pandroid_Black, R.string.black, GlobalConfig.THEME_BLACK)
};
public ThemeSelectorDialog(@NonNull Context context) {
super(context);
View content = LayoutInflater.from(context).inflate(R.layout.dialog_select_theme, null, false);
setContentView(content);
adapter = new SimpleListAdapter<>(R.layout.hold_theme_preview_summary, this::bindItemView);
adapter.clear();
ArrayList<Theme> themeList = new ArrayList<>(Arrays.asList(themes));
themeList.sort((o1, o2) -> o1.id == currentTheme ? -1 : 0);
adapter.addAll(themeList);
((RecyclerView) content.findViewById(R.id.recycler)).setAdapter(adapter);
((RecyclerView) content.findViewById(R.id.recycler)).setLayoutManager(new AutoFitGridLayout(getContext(), 150));
}
private void bindItemView(int i, Theme theme, View view) {
ViewGroup container = view.findViewById(R.id.preview);
container.removeAllViews();
container.addView(LayoutInflater.from(new ContextThemeWrapper(getContext(), theme.style)).inflate(R.layout.hold_theme_preview, null, false));
((TextView) view.findViewById(R.id.title)).setText(theme.name);
((AppCompatRadioButton) view.findViewById(R.id.checkbox)).setChecked(GlobalConfig.get(GlobalConfig.KEY_APP_THEME) == theme.id);
view.setOnClickListener(v -> {
dismiss();
if (theme.id != GlobalConfig.get(GlobalConfig.KEY_APP_THEME)) {
GlobalConfig.set(GlobalConfig.KEY_APP_THEME, theme.id);
((Activity) v.getContext()).recreate();
}
});
}
private static final class Theme {
private final int style;
private final int name;
private final int id;
private Theme(int style, int name, int value) {
this.style = style;
this.name = name;
this.id = value;
}
}
}

View file

@ -0,0 +1,51 @@
package com.panda3ds.pandroid.app.preferences.ds;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.BaseActivity;
import com.panda3ds.pandroid.view.ds.DsEditorView;
import com.panda3ds.pandroid.view.ds.DsLayoutManager;
public class DsEditorPreferences extends Fragment {
private LinearLayout layout;
private DsEditorView editor;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
layout = new LinearLayout(container.getContext());
return layout;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
int index = getArguments().getInt("index");
layout.removeAllViews();
layout.addView(editor = new DsEditorView(view.getContext(), index), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
((BaseActivity)requireActivity()).getSupportActionBar().hide();
}
@Override
public void onResume() {
super.onResume();
requireActivity().getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN|View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_IMMERSIVE);
}
@Override
public void onDestroy() {
super.onDestroy();
DsLayoutManager.save();
Toast.makeText(requireActivity(), R.string.saved, Toast.LENGTH_LONG).show();
}
}

View file

@ -0,0 +1,42 @@
package com.panda3ds.pandroid.app.preferences.ds;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.PreferenceActivity;
import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
import com.panda3ds.pandroid.view.ds.DsLayoutManager;
public class DsListPreferences extends BasePreferenceFragment {
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
setPreferencesFromResource(R.xml.empty_preferences, rootKey);
setActivityTitle(R.string.dual_screen_layouts);
refresh();
}
public void refresh(){
PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
for (int i = 0; i < DsLayoutManager.getLayoutCount(); i++){
Preference pref = new Preference(getPreferenceScreen().getContext());
pref.setIconSpaceReserved(false);
pref.setTitle("Layout "+(i+1));
pref.setSummary(R.string.click_to_change);
pref.setIcon(R.drawable.ic_edit);
pref.setKey(String.valueOf(i));
final int index = i;
pref.setOnPreferenceClickListener(preference -> {
PreferenceActivity.launch(requireContext(), DsEditorPreferences.class, new Intent().putExtra("index", index));
return false;
});
screen.addPreference(pref);
}
}
}

View file

@ -1,5 +1,7 @@
package com.panda3ds.pandroid.data;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.panda3ds.pandroid.lang.Task;
@ -30,7 +32,10 @@ public class GsonConfigParser {
String[] content = new String[] {"{}"};
new Task(()->{
if (FileUtils.exists(getPath())) {
content[0] = FileUtils.readTextFile(getPath());
String src = FileUtils.readTextFile(getPath());
if(src != null && src.length() > 2){
content[0] = src;
}
}
}).runSync();

View file

@ -1,5 +1,7 @@
package com.panda3ds.pandroid.data.config;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.internal.LinkedTreeMap;
import com.panda3ds.pandroid.data.GsonConfigParser;
import com.panda3ds.pandroid.utils.Constants;
@ -10,6 +12,7 @@ import java.util.Map;
public class GlobalConfig {
private static final GsonConfigParser parser = new GsonConfigParser(Constants.PREF_GLOBAL_CONFIG);
private static final Gson gson = new Gson();
public static final int THEME_ANDROID = 0;
public static final int THEME_LIGHT = 1;
@ -23,6 +26,7 @@ public class GlobalConfig {
public static final Key<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<Boolean> KEY_SCREEN_GAMEPAD_VISIBLE = new Key<>("app.screen_gamepad.visible", true);
public static final Key<String> KEY_DS_LAYOUTS = new Key<>("app.ds.layouts", "");
public static void initialize() {
data = parser.load(DataModel.class);
@ -54,6 +58,21 @@ public class GlobalConfig {
writeChanges();
}
public static <T 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() {
parser.save(data);
}
@ -70,6 +89,7 @@ public class GlobalConfig {
private static class DataModel {
private final Map<String, Object> configs = new LinkedTreeMap<>();
private final JsonObject extras = new JsonObject();
public Object get(String key) {
return configs.get(key);

View file

@ -25,6 +25,7 @@ public class InputHandler {
};
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) {
for (int source : sources) {
@ -108,8 +109,17 @@ public class InputHandler {
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;
}
@ -117,5 +127,6 @@ public class InputHandler {
eventListener = null;
motionDeadZone = 0.0f;
motionDownEvents.clear();
keyDownEvents.clear();
}
}

View file

@ -19,7 +19,8 @@ public enum KeyName {
SELECT(Constants.INPUT_KEY_SELECT),
L(Constants.INPUT_KEY_L),
R(Constants.INPUT_KEY_R),
NULL;
NULL,
CHANGE_DS_LAYOUT;
private final int keyId;

View file

@ -0,0 +1,32 @@
package com.panda3ds.pandroid.math;
public class Shape {
public int x = 0, y = 0, width = 1, height = 1;
public Shape() {
}
public Shape(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void move(int x, int y) {
this.x += x;
this.y += y;
}
public void normalize() {
this.x = Math.max(x, 0);
this.y = Math.max(y, 0);
this.width = Math.max(width, 1);
this.height = Math.max(height, 1);
}
public void maxSize(int w, int h) {
this.width = Math.max(w, this.width);
this.height = Math.max(h, this.height);
}
}

View file

@ -2,24 +2,26 @@ package com.panda3ds.pandroid.view;
import static android.opengl.GLES32.*;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.opengl.GLSurfaceView;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.panda3ds.pandroid.AlberDriver;
import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.app.base.BottomAlertDialog;
import com.panda3ds.pandroid.data.SMDH;
import com.panda3ds.pandroid.data.config.GlobalConfig;
import com.panda3ds.pandroid.data.game.GameMetadata;
import com.panda3ds.pandroid.utils.Constants;
import com.panda3ds.pandroid.utils.GameUtils;
import com.panda3ds.pandroid.utils.PerformanceMonitor;
import com.panda3ds.pandroid.view.ds.DsLayoutManager;
import com.panda3ds.pandroid.view.renderer.ConsoleRenderer;
import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout;
import com.panda3ds.pandroid.view.renderer.layout.DefaultScreenLayout;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
@ -38,7 +40,7 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
setLayout(new DefaultScreenLayout());
setLayout(DsLayoutManager.createLayout(0));
}
@Override
@ -74,9 +76,9 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
glGenTextures(1, generateBuffer, 0);
screenTexture = generateBuffer[0];
glBindTexture(GL_TEXTURE_2D, screenTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, screenWidth, screenHeight);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, Constants.N3DS_WIDTH, Constants.N3DS_FULL_HEIGHT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, generateBuffer, 0);
@ -95,19 +97,17 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
if (!AlberDriver.LoadRom(romPath)) {
// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(context.getMainLooper());
Runnable runnable = new Runnable() {
@Override
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Failed to load ROM")
.setMessage("Make sure it's a valid 3DS ROM and that storage permissions are configured properly.")
.setPositiveButton("OK", null)
mainHandler.post(()-> {
new BottomAlertDialog(context)
.setTitle(R.string.failed_load_rom)
.setMessage(R.string.dialog_message_invalid_rom)
.setPositiveButton(android.R.string.ok, (dialog, witch) -> {
dialog.dismiss();
((Activity) context).finish();
})
.setCancelable(false)
.show();
}
};
mainHandler.post(runnable);
});
GameMetadata game = GameUtils.getCurrentGame();
GameUtils.removeGame(game);
@ -131,6 +131,9 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
}
public void onDrawFrame(GL10 unused) {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (AlberDriver.HasRomLoaded()) {
AlberDriver.RunFrame(screenFbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

View file

@ -0,0 +1,397 @@
package com.panda3ds.pandroid.view.ds;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.appcompat.widget.AppCompatSpinner;
import androidx.appcompat.widget.AppCompatTextView;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.panda3ds.pandroid.R;
import com.panda3ds.pandroid.math.Shape;
import com.panda3ds.pandroid.math.Vector2;
import com.panda3ds.pandroid.utils.Constants;
public class DsEditorView extends FrameLayout {
private static final int COLOR_TOP_SELECTION = Color.RED;
private static final int COLOR_BOTTOM_SELECTION = Color.BLUE;
private final float SIZE_DP;
private final Paint selectionPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final DsLayout layout;
private int width = 1, height = 1;
private final LinearLayout gravityAnchor;
private final LinearLayout aspectRatioFixLayout;
private final LinearLayout modeSelectorLayout;
private final AppCompatSpinner modeSelector;
private final PointView spacePoint;
private final PointView topDisplay;
private final PointView bottomDisplay;
private final PointView topDisplayResizer;
private final PointView bottomDisplayResizer;
@SuppressLint("ClickableViewAccessibility")
public DsEditorView(Context context, int index) {
super(context);
layout = (DsLayout) DsLayoutManager.createLayout(index);
SIZE_DP = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
selectionPaint.setColor(COLOR_TOP_SELECTION);
selectionPaint.setStrokeWidth(SIZE_DP * 2);
selectionPaint.setPathEffect(new DashPathEffect(new float[]{SIZE_DP * 10, SIZE_DP * 10}, 0.0f));
selectionPaint.setStyle(Paint.Style.STROKE);
layout.setTopDisplaySourceSize(Constants.N3DS_WIDTH, Constants.N3DS_HALF_HEIGHT);
layout.setBottomDisplaySourceSize(Constants.N3DS_WIDTH - 40 - 40, Constants.N3DS_HALF_HEIGHT);
setBackgroundColor(Color.argb(2, 0, 0, 0));
LayoutInflater inflater = LayoutInflater.from(context);
gravityAnchor = (LinearLayout) inflater.inflate(R.layout.ds_editor_gravity_anchor, this, false);
gravityAnchor.findViewById(R.id.up).setOnClickListener(v -> {
layout.getCurrentModel().gravity = Gravity.TOP;
refreshLayout();
});
gravityAnchor.findViewById(R.id.center).setOnClickListener(v -> {
layout.getCurrentModel().gravity = Gravity.CENTER;
refreshLayout();
});
gravityAnchor.findViewById(R.id.down).setOnClickListener(v -> {
layout.getCurrentModel().gravity = Gravity.BOTTOM;
refreshLayout();
});
gravityAnchor.findViewById(R.id.revert).setOnClickListener(v -> {
layout.getCurrentModel().reverse = !layout.getCurrentModel().reverse;
refreshLayout();
});
{
modeSelectorLayout = (LinearLayout) inflater.inflate(R.layout.ds_editor_spinner, this, false);
ArrayAdapter<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;
if (checked) {
fixAspect();
}
refreshPoints();
});
}
spacePoint = new PointView();
spacePoint.setColor(Color.WHITE, COLOR_TOP_SELECTION);
spacePoint.setOnTouchListener((view, motion) -> {
layout.getCurrentModel().space = (motion.getX() + spacePoint.x()) / (float) width;
refreshPoints();
return true;
});
spacePoint.setLayoutGravity(Gravity.START | Gravity.CENTER);
setOnClickListener(v -> {
if (layout.getCurrentModel().mode == Mode.SINGLE) {
layout.getCurrentModel().singleTop = !layout.getCurrentModel().singleTop;
refreshPoints();
}
});
topDisplay = new PointView();
topDisplay.setText(R.string.top_display);
topDisplay.setOnTouchListener(new DisplayTouchEvent(true));
topDisplay.setBackground(new SelectionDrawable(COLOR_TOP_SELECTION));
bottomDisplay = new PointView();
bottomDisplay.setText(R.string.bottom_display);
bottomDisplay.setOnTouchListener(new DisplayTouchEvent(false));
bottomDisplay.setBackground(new SelectionDrawable(COLOR_BOTTOM_SELECTION));
topDisplayResizer = new PointView();
topDisplayResizer.setColor(0, COLOR_TOP_SELECTION);
topDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(true));
bottomDisplayResizer = new PointView();
bottomDisplayResizer.setColor(0, COLOR_BOTTOM_SELECTION);
bottomDisplayResizer.setOnTouchListener(new DisplayResizeTouchEvent(false));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
if (this.width != width || this.height != height) {
this.width = width;
this.height = height;
refreshLayout();
}
}
private void fixAspect() {
Shape top = layout.getCurrentModel().preferredTop;
Shape bottom = layout.getCurrentModel().preferredBottom;
top.height = (int) (((float) top.width / Constants.N3DS_WIDTH) * Constants.N3DS_HALF_HEIGHT);
bottom.height = (int) (((float) bottom.width / (Constants.N3DS_WIDTH - 80)) * Constants.N3DS_HALF_HEIGHT);
}
private void refreshPoints() {
Model data = layout.getCurrentModel();
layout.update(width, height);
Rect bottomDisplay = layout.getBottomDisplayBounds();
Rect topDisplay = layout.getTopDisplayBounds();
switch (data.mode) {
case RELATIVE: {
if (width > height) {
Rect primaryDisplay = data.reverse ? bottomDisplay : topDisplay;
data.space = primaryDisplay.width() / (float) width;
spacePoint.setCenterPosition(primaryDisplay.width(), (int) (SIZE_DP * 15));
spacePoint.setText(String.valueOf((int) (data.space * 100)));
}
}
break;
case SINGLE:
case ABSOLUTE: {
}
break;
}
data.preferredTop.maxSize((int) (SIZE_DP * 64), (int) (SIZE_DP * 64));
data.preferredBottom.maxSize((int) (SIZE_DP * 64), (int) (SIZE_DP * 64));
this.topDisplay.setSize(topDisplay.width(), topDisplay.height());
this.topDisplay.setPosition(topDisplay.left, topDisplay.top);
this.bottomDisplay.setSize(bottomDisplay.width(), bottomDisplay.height());
this.bottomDisplay.setPosition(bottomDisplay.left, bottomDisplay.top);
if (data.lockAspect) {
topDisplayResizer.setCenterPosition(topDisplay.right, topDisplay.top + (topDisplay.height() / 2));
bottomDisplayResizer.setCenterPosition(bottomDisplay.right, bottomDisplay.top + (bottomDisplay.height() / 2));
} else {
topDisplayResizer.setCenterPosition(topDisplay.right, topDisplay.bottom);
bottomDisplayResizer.setCenterPosition(bottomDisplay.right, bottomDisplay.bottom);
}
invalidate();
}
private void refreshLayout() {
removeAllViews();
layout.update(width, height);
boolean landscape = width > height;
addView(topDisplay);
addView(bottomDisplay);
gravityAnchor.setOrientation(LinearLayout.HORIZONTAL);
addView(modeSelectorLayout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER));
switch (layout.getCurrentModel().mode) {
case RELATIVE: {
addView(gravityAnchor, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.CENTER | Gravity.TOP));
if (landscape) {
addView(spacePoint);
}
}
break;
case ABSOLUTE: {
addView(aspectRatioFixLayout, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.CENTER | Gravity.TOP));
addView(topDisplayResizer);
addView(bottomDisplayResizer);
}
break;
case SINGLE: {
addView(aspectRatioFixLayout, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity.CENTER | Gravity.TOP));
}
break;
}
((MaterialCheckBox) aspectRatioFixLayout.findViewById(R.id.checkbox)).setChecked(layout.getCurrentModel().lockAspect);
modeSelector.setSelection(layout.getCurrentModel().mode.ordinal());
gravityAnchor.findViewById(R.id.revert).setRotation(landscape ? 0 : 90);
refreshPoints();
}
private class PointView extends AppCompatTextView {
public PointView() {
super(DsEditorView.this.getContext());
setLayoutParams(new FrameLayout.LayoutParams((int) (SIZE_DP * 30), (int) (SIZE_DP * 30)));
setBackgroundResource(R.drawable.medium_card_background);
setGravity(Gravity.CENTER);
this.setFocusable(true);
this.setClickable(true);
}
public int x() {
return ((LayoutParams) getLayoutParams()).leftMargin;
}
public int y() {
return ((LayoutParams) getLayoutParams()).topMargin;
}
public int width() {
return ((LayoutParams) getLayoutParams()).width;
}
public void setColor(int text, int background) {
setTextColor(text);
setBackgroundTintList(ColorStateList.valueOf(background));
}
public void setSize(int width, int height) {
LayoutParams params = (LayoutParams) getLayoutParams();
params.width = Math.max(0, width);
params.height = Math.max(0, height);
setLayoutParams(params);
}
public void setPosition(int x, int y) {
LayoutParams params = (LayoutParams) getLayoutParams();
params.leftMargin = x;
params.topMargin = y;
setLayoutParams(params);
}
public void setCenterPosition(int x, int y) {
int middle = this.width() / 2;
setPosition(Math.max(-middle, Math.min(x - middle, width - middle)), Math.max(-middle, Math.min(y - middle, height - middle)));
}
public void setLayoutGravity(int gravity) {
FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams();
params.gravity = gravity;
setLayoutParams(params);
}
}
private class DisplayTouchEvent implements OnTouchListener {
private final boolean topScreen;
private final Vector2 inner = new Vector2(0, 0);
private Vector2 downEvent = null;
private DisplayTouchEvent(boolean topScreen) {
this.topScreen = topScreen;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Shape preferred = topScreen ? layout.getCurrentModel().preferredTop : layout.getCurrentModel().preferredBottom;
if (layout.getCurrentModel().mode == Mode.ABSOLUTE) {
PointView point = (PointView) v;
if (event.getAction() != MotionEvent.ACTION_UP) {
if (downEvent == null) {
downEvent = new Vector2(event.getRawX(), event.getRawY());
inner.set(point.x(), point.y());
return true;
}
preferred.x = (int) ((event.getRawX() - downEvent.x) + inner.x);
preferred.y = (int) ((event.getRawY() - downEvent.y) + inner.y);
preferred.normalize();
refreshPoints();
return true;
}
downEvent = null;
return false;
} else if (layout.getCurrentModel().mode == Mode.SINGLE && event.getAction() == MotionEvent.ACTION_UP) {
callOnClick();
}
return false;
}
}
private class DisplayResizeTouchEvent implements OnTouchListener {
private final boolean topScreen;
private final Vector2 size = new Vector2(0, 0);
private Vector2 downEvent = null;
private DisplayResizeTouchEvent(boolean topScreen) {
this.topScreen = topScreen;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Shape preferred = topScreen ? layout.getCurrentModel().preferredTop : layout.getCurrentModel().preferredBottom;
if (event.getAction() != MotionEvent.ACTION_UP) {
if (downEvent == null) {
downEvent = new Vector2(event.getRawX(), event.getRawY());
size.set(preferred.width, preferred.height);
return true;
}
preferred.width = (int) (size.x + ((event.getRawX() - downEvent.x)));
if (layout.getCurrentModel().lockAspect) {
fixAspect();
} else {
preferred.height = (int) (size.y + ((event.getRawY() - downEvent.y)));
}
preferred.maxSize((int) (SIZE_DP * 32), (int) (SIZE_DP * 32));
refreshPoints();
return true;
}
downEvent = null;
return false;
}
}
private class SelectionDrawable extends ColorDrawable {
private final Paint solidPaint = new Paint();
public SelectionDrawable(int color) {
super(color);
}
@Override
public void draw(Canvas canvas) {
int color = this.getColor();
selectionPaint.setColor(color);
solidPaint.setColor(Color.argb(50, Color.red(color), Color.green(color), Color.blue(color)));
canvas.drawRect(this.getBounds(), solidPaint);
canvas.drawRect(this.getBounds(), selectionPaint);
}
}
}

View file

@ -0,0 +1,183 @@
package com.panda3ds.pandroid.view.ds;
import android.graphics.Rect;
import android.view.Gravity;
import com.panda3ds.pandroid.math.Shape;
import com.panda3ds.pandroid.math.Vector2;
import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout;
class DsLayout implements ConsoleLayout {
private final Rect topDisplay = new Rect();
private final Rect bottomDisplay = new Rect();
private final Vector2 screenSize = new Vector2(0,0);
private final Vector2 sourceTop = new Vector2(0,0);
private final Vector2 sourceBottom = new Vector2(0,0);
private final Model[] modes = new Model[2];
public DsLayout(Model landscape, Model portrait){
modes[0] = landscape;
modes[1] = portrait;
}
public DsLayout(){
this(new Model(), new Model());
}
@Override
public void update(int screenWidth, int screenHeight) {
screenSize.set(screenWidth, screenHeight);
update();
}
@Override
public void setBottomDisplaySourceSize(int width, int height) {
sourceBottom.set(width, height);
update();
}
@Override
public void setTopDisplaySourceSize(int width, int height) {
sourceTop.set(width, height);
update();
}
@Override
public Rect getBottomDisplayBounds() {
return bottomDisplay;
}
@Override
public Rect getTopDisplayBounds() {
return topDisplay;
}
public void update(){
Model data = getCurrentModel();
Mode mode = data.mode;
switch (mode){
case RELATIVE:
relative(data);
break;
case SINGLE:
single(data);
break;
case ABSOLUTE:
absolute(data);
break;
}
}
private void absolute(Model data) {
Shape top = data.preferredTop;
Shape bottom = data.preferredBottom;
top.normalize();
bottom.normalize();
topDisplay.set(top.x, top.y, top.x +top.width, top.y + top.height);
bottomDisplay.set(bottom.x, bottom.y, bottom.x +bottom.width, bottom.y + bottom.height);
}
/**
* SINGLE LAYOUT:
* SHOW ONLY SCREEN IN FIT MODE
*/
private void single(Model data) {
Vector2 source = data.singleTop ? sourceTop : sourceBottom;
Rect dest = data.singleTop ? topDisplay : bottomDisplay;
if (data.lockAspect) {
int x = 0, y = 0;
int width = (int) ((screenSize.y / source.y) * source.x);
int height;
if (width > screenSize.x) {
height = (int) ((screenSize.x / source.x) * source.y);
width = (int) screenSize.x;
y = (int) ((screenSize.y - height) / 2);
} else {
height = (int) screenSize.y;
x = (int) ((screenSize.x - width) / 2);
}
dest.set(x,y,x+width,y+height);
} else {
dest.set(0,0, (int) screenSize.x, (int) screenSize.y);
}
(data.singleTop ? bottomDisplay : topDisplay).set(0,0,0,0);
}
/***
* RELATIVE LAYOUT:
* ORGANIZE SCREEN IN POSITION BASED IN GRAVITY
* AND SPACE, THE SPACE DETERMINE LANDSCAPE TOP SCREEN SIZE
*/
private void relative(Model data) {
int screenWidth = (int) screenSize.x;
int screenHeight = (int) screenSize.y;
Vector2 topSourceSize = this.sourceTop;
Vector2 bottomSourceSize = this.sourceBottom;
Rect topDisplay = this.topDisplay;
Rect bottomDisplay = this.bottomDisplay;
if (data.reverse){
topSourceSize = this.sourceBottom;
bottomSourceSize = this.sourceTop;
topDisplay = this.bottomDisplay;
bottomDisplay = this.topDisplay;
}
if (screenWidth > screenHeight) {
int topDisplayWidth = (int) ((screenHeight / topSourceSize.y) * topSourceSize.x);
int topDisplayHeight = screenHeight;
if (topDisplayWidth > (screenWidth * data.space)) {
topDisplayWidth = (int) (screenWidth * data.space);
topDisplayHeight = (int) ((topDisplayWidth / topSourceSize.x) * topSourceSize.y);
}
int bottomDisplayHeight = (int) (((screenWidth - topDisplayWidth) / bottomSourceSize.x) * bottomSourceSize.y);
topDisplay.set(0, 0, topDisplayWidth, topDisplayHeight);
bottomDisplay.set(topDisplayWidth, 0, topDisplayWidth + (screenWidth - topDisplayWidth), bottomDisplayHeight);
switch (data.gravity){
case Gravity.CENTER:{
bottomDisplay.offset(0, (screenHeight-bottomDisplay.height())/2);
topDisplay.offset(0, (screenHeight-topDisplay.height())/2);
}break;
case Gravity.BOTTOM:{
bottomDisplay.offset(0, (screenHeight-bottomDisplay.height()));
topDisplay.offset(0, (screenHeight-topDisplay.height()));
}break;
}
} else {
int topScreenHeight = (int) ((screenWidth / topSourceSize.x) * topSourceSize.y);
topDisplay.set(0, 0, screenWidth, topScreenHeight);
int bottomDisplayHeight = (int) ((screenWidth / bottomSourceSize.x) * bottomSourceSize.y);
int bottomDisplayWidth = screenWidth;
int bottomDisplayX = 0;
if (topScreenHeight + bottomDisplayHeight > screenHeight) {
bottomDisplayHeight = (screenHeight - topScreenHeight);
bottomDisplayWidth = (int) ((bottomDisplayHeight / bottomSourceSize.y) * bottomSourceSize.x);
bottomDisplayX = (screenWidth - bottomDisplayX) / 2;
}
topDisplay.set(0, 0, screenWidth, topScreenHeight);
bottomDisplay.set(bottomDisplayX, topScreenHeight, bottomDisplayX + bottomDisplayWidth, topScreenHeight + bottomDisplayHeight);
}
}
public Model getCurrentModel() {
return screenSize.x > screenSize.y ? modes[0] : modes[1];
}
}

View file

@ -0,0 +1,52 @@
package com.panda3ds.pandroid.view.ds;
import com.panda3ds.pandroid.data.config.GlobalConfig;
import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout;
import java.util.ArrayList;
public class DsLayoutManager {
private static final DataModel data;
static {
data = GlobalConfig.getExtra(GlobalConfig.KEY_DS_LAYOUTS, DataModel.class);
if (data.models.size() == 0){
setupBasicModels();
}
}
private static void setupBasicModels() {
Model model1 = new Model();
Model model2 = new Model();
model2.mode = Mode.SINGLE;
model2.singleTop = false;
Model model3 = new Model();
model3.mode = Mode.SINGLE;
model3.singleTop = true;
data.models.add(new Model[]{model1, model1.clone()});
data.models.add(new Model[]{model2, model2.clone()});
data.models.add(new Model[]{model3, model3.clone()});
save();
}
public static synchronized void save(){
GlobalConfig.putExtra(GlobalConfig.KEY_DS_LAYOUTS, data);
}
public static int getLayoutCount(){
return data.models.size();
}
public static ConsoleLayout createLayout(int index){
index = Math.min(getLayoutCount()-1, index);
return new DsLayout(data.models.get(index)[0],data.models.get(index)[1]);
}
private static class DataModel {
private final ArrayList<Model[]> models = new ArrayList<>();
}
}

View file

@ -0,0 +1,7 @@
package com.panda3ds.pandroid.view.ds;
enum Mode {
SINGLE,
RELATIVE,
ABSOLUTE
}

View file

@ -0,0 +1,31 @@
package com.panda3ds.pandroid.view.ds;
import android.util.Log;
import android.view.Gravity;
import androidx.annotation.NonNull;
import com.panda3ds.pandroid.math.Shape;
import com.panda3ds.pandroid.utils.Constants;
class Model implements Cloneable {
public Mode mode = Mode.RELATIVE;
public final Shape preferredTop = new Shape();
public final Shape preferredBottom = new Shape();
public boolean reverse = false;
public boolean singleTop = true;
public float space = 0.6f;
public int gravity = Gravity.CENTER;
public boolean lockAspect = true;
@NonNull
@Override
public Model clone() {
try {
return (Model) super.clone();
} catch (Exception e){
Log.e(Constants.LOG_TAG, "Error on clone DsModel!", e);
return new Model();
}
}
}

View file

@ -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;
}
}

View file

@ -1,10 +1,11 @@
package com.panda3ds.pandroid.view.renderer.layout;
import android.graphics.Rect;
import android.view.Gravity;
import com.panda3ds.pandroid.math.Vector2;
public class DefaultScreenLayout implements ConsoleLayout {
public class RelativeScreenLayout implements ConsoleLayout {
private final Rect topDisplay = new Rect();
private final Rect bottomDisplay = new Rect();
@ -12,6 +13,12 @@ public class DefaultScreenLayout implements ConsoleLayout {
private final Vector2 topSourceSize = new Vector2(1.0f, 1.0f);
private final Vector2 bottomSourceSize = new Vector2(1.0f, 1.0f);
private boolean landscapeReverse = false;
private boolean portraitReverse = false;
private float landscapeSpace = 0.6f;
private int landscapeGravity = Gravity.CENTER;
private int portraitGravity = Gravity.TOP;
@Override
public void update(int screenWidth, int screenHeight) {
screenSize.set(screenWidth, screenHeight);
@ -29,16 +36,50 @@ public class DefaultScreenLayout implements ConsoleLayout {
updateBounds();
}
public void setPortraitGravity(int portraitGravity) {
this.portraitGravity = portraitGravity;
}
public void setLandscapeGravity(int landscapeGravity) {
this.landscapeGravity = landscapeGravity;
}
public void setLandscapeSpace(float landscapeSpace) {
this.landscapeSpace = landscapeSpace;
}
public void setLandscapeReverse(boolean landscapeReverse) {
this.landscapeReverse = landscapeReverse;
}
public void setPortraitReverse(boolean portraitReverse) {
this.portraitReverse = portraitReverse;
}
private void updateBounds() {
int screenWidth = (int) screenSize.x;
int screenHeight = (int) screenSize.y;
Vector2 topSourceSize = this.topSourceSize;
Vector2 bottomSourceSize = this.bottomSourceSize;
Rect topDisplay = this.topDisplay;
Rect bottomDisplay = this.bottomDisplay;
if ((landscapeReverse && screenWidth > screenHeight) || (portraitReverse && screenWidth < screenHeight)){
topSourceSize = this.bottomSourceSize;
bottomSourceSize = this.topSourceSize;
topDisplay = this.bottomDisplay;
bottomDisplay = this.topDisplay;
}
if (screenWidth > screenHeight) {
int topDisplayWidth = (int) ((screenHeight / topSourceSize.y) * topSourceSize.x);
int topDisplayHeight = screenHeight;
if (topDisplayWidth > (screenWidth * 0.7)) {
topDisplayWidth = (int) (screenWidth * 0.7);
if (topDisplayWidth > (screenWidth * landscapeSpace)) {
topDisplayWidth = (int) (screenWidth * landscapeSpace);
topDisplayHeight = (int) ((topDisplayWidth / topSourceSize.x) * topSourceSize.y);
}
@ -46,6 +87,7 @@ public class DefaultScreenLayout implements ConsoleLayout {
topDisplay.set(0, 0, topDisplayWidth, topDisplayHeight);
bottomDisplay.set(topDisplayWidth, 0, topDisplayWidth + (screenWidth - topDisplayWidth), bottomDisplayHeight);
adjustHorizontalGravity();
} else {
int topScreenHeight = (int) ((screenWidth / topSourceSize.x) * topSourceSize.y);
topDisplay.set(0, 0, screenWidth, topScreenHeight);
@ -62,9 +104,42 @@ public class DefaultScreenLayout implements ConsoleLayout {
topDisplay.set(0, 0, screenWidth, topScreenHeight);
bottomDisplay.set(bottomDisplayX, topScreenHeight, bottomDisplayX + bottomDisplayWidth, topScreenHeight + bottomDisplayHeight);
adjustVerticalGravity();
}
}
private void adjustHorizontalGravity(){
int topOffset = 0;
int bottomOffset = 0;
switch (landscapeGravity){
case Gravity.CENTER:{
topOffset = (int) (screenSize.y - topDisplay.height())/2;
bottomOffset = (int) (screenSize.y - bottomDisplay.height())/2;
}break;
case Gravity.BOTTOM:{
topOffset = (int) (screenSize.y - topDisplay.height());
bottomOffset = (int) (screenSize.y - bottomDisplay.height());
}break;
}
topDisplay.offset(0, topOffset);
bottomDisplay.offset(0, bottomOffset);
}
private void adjustVerticalGravity(){
int height = (topDisplay.height() + bottomDisplay.height());
int space = 0;
switch (portraitGravity){
case Gravity.CENTER:{
space = (int) (screenSize.y - height)/2;
}break;
case Gravity.BOTTOM:{
space = (int) (screenSize.y - height);
}break;
}
topDisplay.offset(0, space);
bottomDisplay.offset(0,space);
}
@Override
public Rect getBottomDisplayBounds() {
return bottomDisplay;

View file

@ -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;
}
}

View file

@ -3,6 +3,7 @@
<item>
<shape>
<corners android:radius="20dp"/>
<stroke android:width="1dp" android:color="?colorSurfaceVariant"/>
<solid android:color="?colorSurface"/>
</shape>
</item>

View file

@ -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>

View file

@ -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="M8,19h3v4h2v-4h3l-4,-4 -4,4zM16,5h-3L13,1h-2v4L8,5l4,4 4,-4zM4,11v2h16v-2L4,11z"/>
</vector>

View file

@ -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="M7.41,8.59 L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
</vector>

View file

@ -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="M7.41,15.41 L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
</vector>

View file

@ -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="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3zM14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4z"/>
</vector>

View file

@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#000000"
<vector android:height="24dp" android:tint="?colorOnSurface"
android:viewportHeight="24" android:viewportWidth="24"
android: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"/>

View file

@ -0,0 +1,22 @@
<?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="wrap_content"
android:gravity="center"
android:background="@drawable/alert_dialog_background">
<View
android:layout_width="120dp"
android:layout_height="4dp"
android:background="?colorSurfaceVariant"
android:layout_margin="8dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="400dp"
android:paddingHorizontal="20dp"/>
</LinearLayout>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,28 @@
<?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">
<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>

View file

@ -0,0 +1,11 @@
<?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="?colorOnPrimary"
android:textSize="14sp"
android:gravity="start|center">
</TextView>

View file

@ -69,16 +69,12 @@
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- Drawer content layout -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="start"
android:layout_marginTop="195dp">
android:gravity="start">
<com.google.android.material.navigation.NavigationView
android:id="@+id/menu"
@ -91,4 +87,7 @@
</LinearLayout>
</LinearLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View 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>

View file

@ -0,0 +1,38 @@
<?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="center">
<com.google.android.material.card.MaterialCardView
android:id="@+id/preview"
android:layout_width="match_parent"
app:strokeColor="?colorSurfaceVariant"
android:layout_height="150dp">
</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: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>

View file

@ -56,4 +56,17 @@
<string name="graphics">Gráficos</string>
<string name="loading">Carregando</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ções para as duas telas.</string>
<string name="dual_screen_layouts_summary">Altere as disposições disponiveis para as telas do console</string>
</resources>

View file

@ -60,4 +60,18 @@
<string name="pref_shader_jit_summary">Use shader recompiler.</string>
<string name="graphics">Graphics</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 configurations.</string>
<string name="bottom_display">Bottom Display</string>
<string name="top_display">Top Display</string>
<string name="fix_aspect">Fix aspect</string>
<string name="dual_screen_layouts">Dual Screen layouts</string>
<string name="dual_screen_layouts_summary">Change layout of console screens.</string>
<string name="click_to_change">Click to change</string>
</resources>

View file

@ -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>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
</PreferenceScreen>

View file

@ -0,0 +1,22 @@
<?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/graphics"
app:iconSpaceReserved="false">
</PreferenceCategory>
</PreferenceScreen>

View file

@ -133,6 +133,12 @@
app:summary="none"
app:iconSpaceReserved="false"/>
<Preference
app:title="Switch dual screen layout"
app:key="CHANGE_DS_LAYOUT"
app:summary="none"
app:iconSpaceReserved="false"/>
</PreferenceCategory>
</PreferenceScreen>

View file

@ -4,6 +4,7 @@
android:divider="#F00">
<Preference
android:key="application"
app:title="@string/app_name"
app:enabled="false"
app:summary="1.0"
@ -17,10 +18,10 @@
app:layout="@layout/preference_start_item"/>
<Preference
app:key="appearance"
app:icon="@drawable/ic_theme"
app:title="@string/appearance"
app:summary="@string/pref_appearance_summary"
app:key="general"
app:icon="@drawable/ic_settings"
app:title="@string/general"
app:summary="@string/pref_general_summary"
app:layout="@layout/preference_start_item"/>
<Preference