mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-06 14:15:41 +12:00
Add scripting support for Android (#369)
* Lua Patcher and code editor: initial commit * Code Editor + Lua Patchs + Fixes * some fixes * bonk --------- Co-authored-by: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>
This commit is contained in:
parent
23770e2704
commit
b5718010ee
35 changed files with 1639 additions and 54 deletions
|
@ -80,6 +80,12 @@ AlberFunction(void, LoadRom)(JNIEnv* env, jobject obj, jstring path) {
|
|||
env->ReleaseStringUTFChars(path, pathStr);
|
||||
}
|
||||
|
||||
AlberFunction(void, LoadLuaScript)(JNIEnv* env, jobject obj, jstring script) {
|
||||
const char* scriptStr = env->GetStringUTFChars(script, nullptr);
|
||||
emulator->getLua().loadString(scriptStr);
|
||||
env->ReleaseStringUTFChars(script, scriptStr);
|
||||
}
|
||||
|
||||
AlberFunction(void, TouchScreenDown)(JNIEnv* env, jobject obj, jint x, jint y) { hidService->setTouchScreenPress((u16)x, (u16)y); }
|
||||
AlberFunction(void, TouchScreenUp)(JNIEnv* env, jobject obj) { hidService->releaseTouchScreen(); }
|
||||
AlberFunction(void, KeyUp)(JNIEnv* env, jobject obj, jint keyCode) { hidService->releaseKey((u32)keyCode); }
|
||||
|
|
|
@ -35,6 +35,11 @@
|
|||
android:name=".app.GameActivity"
|
||||
android:configChanges="screenSize|screenLayout|orientation|density|uiMode">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".app.editor.CodeEditorActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="screenSize|screenLayout|orientation|density|uiMode">
|
||||
</activity>
|
||||
<activity android:name=".app.PreferenceActivity"
|
||||
android:launchMode="standard"
|
||||
android:configChanges="screenSize|screenLayout|orientation|density"/>
|
||||
|
|
BIN
src/pandroid/app/src/main/assets/fonts/comic_mono.ttf
Normal file
BIN
src/pandroid/app/src/main/assets/fonts/comic_mono.ttf
Normal file
Binary file not shown.
|
@ -19,7 +19,7 @@ public class AlberDriver {
|
|||
public static native void TouchScreenDown(int x, int y);
|
||||
public static native void Pause();
|
||||
public static native void Resume();
|
||||
|
||||
public static native void LoadLuaScript(String script);
|
||||
public static native byte[] GetSmdh();
|
||||
|
||||
static { System.loadLibrary("Alber"); }
|
||||
|
|
|
@ -8,7 +8,7 @@ import com.panda3ds.pandroid.data.config.GlobalConfig;
|
|||
|
||||
|
||||
public class BaseActivity extends AppCompatActivity {
|
||||
private int currentTheme = GlobalConfig.get(GlobalConfig.KEY_APP_THEME);
|
||||
private int currentTheme = PandroidApplication.getThemeId();
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
@ -20,19 +20,13 @@ public class BaseActivity extends AppCompatActivity {
|
|||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (GlobalConfig.get(GlobalConfig.KEY_APP_THEME) != currentTheme) {
|
||||
if (PandroidApplication.getThemeId() != currentTheme) {
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyTheme() {
|
||||
switch (GlobalConfig.get(GlobalConfig.KEY_APP_THEME)) {
|
||||
case GlobalConfig.THEME_ANDROID: setTheme(R.style.Theme_Pandroid); break;
|
||||
case GlobalConfig.THEME_LIGHT: setTheme(R.style.Theme_Pandroid_Light); break;
|
||||
case GlobalConfig.THEME_DARK: setTheme(R.style.Theme_Pandroid_Dark); break;
|
||||
case GlobalConfig.THEME_BLACK: setTheme(R.style.Theme_Pandroid_Black); break;
|
||||
}
|
||||
|
||||
currentTheme = GlobalConfig.get(GlobalConfig.KEY_APP_THEME);
|
||||
currentTheme = PandroidApplication.getThemeId();
|
||||
setTheme(currentTheme);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,7 @@ import com.panda3ds.pandroid.view.PandaLayoutController;
|
|||
|
||||
public class GameActivity extends BaseActivity {
|
||||
private final DrawerFragment drawerFragment = new DrawerFragment();
|
||||
private final AlberInputListener inputListener = new AlberInputListener(() -> {
|
||||
if (drawerFragment.isOpened()) {
|
||||
drawerFragment.close();
|
||||
} else {
|
||||
drawerFragment.open();
|
||||
}
|
||||
});
|
||||
private final AlberInputListener inputListener = new AlberInputListener(this::onBackPressed);
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
@ -84,16 +78,25 @@ public class GameActivity extends BaseActivity {
|
|||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (InputHandler.processKeyEvent(event)) {
|
||||
if ((!drawerFragment.isOpened()) && InputHandler.processKeyEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (drawerFragment.isOpened()) {
|
||||
drawerFragment.close();
|
||||
} else {
|
||||
drawerFragment.open();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchGenericMotionEvent(MotionEvent ev) {
|
||||
if (InputHandler.processMotionEvent(ev)) {
|
||||
if ((!drawerFragment.isOpened()) && InputHandler.processMotionEvent(ev)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,10 +15,13 @@ import androidx.fragment.app.Fragment;
|
|||
import androidx.fragment.app.FragmentManager;
|
||||
import com.google.android.material.navigation.NavigationBarView;
|
||||
import com.panda3ds.pandroid.R;
|
||||
import com.panda3ds.pandroid.app.editor.CodeEditorActivity;
|
||||
import com.panda3ds.pandroid.app.main.GamesFragment;
|
||||
import com.panda3ds.pandroid.app.main.SearchFragment;
|
||||
import com.panda3ds.pandroid.app.main.SettingsFragment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
public class MainActivity extends BaseActivity implements NavigationBarView.OnItemSelectedListener {
|
||||
private static final int PICK_ROM = 2;
|
||||
|
@ -28,13 +31,6 @@ public class MainActivity extends BaseActivity implements NavigationBarView.OnIt
|
|||
private final SearchFragment searchFragment = new SearchFragment();
|
||||
private final SettingsFragment settingsFragment = new SettingsFragment();
|
||||
|
||||
private void openFile() {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
startActivityForResult(intent, PICK_ROM);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
|
@ -2,7 +2,11 @@ package com.panda3ds.pandroid.app;
|
|||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.panda3ds.pandroid.AlberDriver;
|
||||
import com.panda3ds.pandroid.R;
|
||||
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||
import com.panda3ds.pandroid.input.InputMap;
|
||||
import com.panda3ds.pandroid.utils.GameUtils;
|
||||
|
@ -22,5 +26,32 @@ public class PandroidApplication extends Application {
|
|||
AlberDriver.Setup();
|
||||
}
|
||||
|
||||
public static int getThemeId() {
|
||||
switch (GlobalConfig.get(GlobalConfig.KEY_APP_THEME)) {
|
||||
case GlobalConfig.THEME_LIGHT:
|
||||
return R.style.Theme_Pandroid_Light;
|
||||
case GlobalConfig.THEME_DARK:
|
||||
return R.style.Theme_Pandroid_Dark;
|
||||
case GlobalConfig.THEME_BLACK:
|
||||
return R.style.Theme_Pandroid_Black;
|
||||
}
|
||||
|
||||
return R.style.Theme_Pandroid;
|
||||
}
|
||||
|
||||
public static boolean isDarkMode() {
|
||||
switch (GlobalConfig.get(GlobalConfig.KEY_APP_THEME)) {
|
||||
case GlobalConfig.THEME_DARK:
|
||||
case GlobalConfig.THEME_BLACK:
|
||||
return true;
|
||||
case GlobalConfig.THEME_LIGHT:
|
||||
return false;
|
||||
}
|
||||
|
||||
Resources res = Resources.getSystem();
|
||||
int nightFlags = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
return nightFlags == Configuration.UI_MODE_NIGHT_YES;
|
||||
}
|
||||
|
||||
public static Context getAppContext() { return appContext; }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package com.panda3ds.pandroid.app.base;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.panda3ds.pandroid.R;
|
||||
|
||||
public class BottomDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public int getTheme() {
|
||||
return R.style.AlertDialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||
dialog.getWindow().setGravity(Gravity.CENTER | Gravity.BOTTOM);
|
||||
dialog.getWindow().getAttributes().y = Math.round(getContext().getResources().getDisplayMetrics().density * 15);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package com.panda3ds.pandroid.app.editor;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import androidx.activity.result.contract.ActivityResultContract;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
import com.panda3ds.pandroid.R;
|
||||
import com.panda3ds.pandroid.app.BaseActivity;
|
||||
import com.panda3ds.pandroid.app.base.BottomAlertDialog;
|
||||
import com.panda3ds.pandroid.lang.Task;
|
||||
import com.panda3ds.pandroid.utils.FileUtils;
|
||||
import com.panda3ds.pandroid.view.code.CodeEditor;
|
||||
import com.panda3ds.pandroid.view.code.syntax.CodeSyntax;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class CodeEditorActivity extends BaseActivity {
|
||||
private static final String TAB = " ";
|
||||
private String path;
|
||||
private String fileName;
|
||||
private CodeEditor editor;
|
||||
private AppCompatTextView title;
|
||||
private View saveButton;
|
||||
private boolean changed = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_code_editor);
|
||||
Arguments args = (Arguments) getIntent().getSerializableExtra("args");
|
||||
|
||||
editor = findViewById(R.id.editor);
|
||||
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(this::onGlobalLayoutChanged);
|
||||
|
||||
path = args.path;
|
||||
fileName = args.fileName;
|
||||
title = findViewById(R.id.title);
|
||||
title.setText(fileName);
|
||||
|
||||
saveButton = findViewById(R.id.save);
|
||||
|
||||
saveButton.setVisibility(View.GONE);
|
||||
saveButton.setOnClickListener(v -> save());
|
||||
|
||||
new Task(() -> {
|
||||
String content = FileUtils.readTextFile(path + "/" + fileName);
|
||||
|
||||
editor.post(() -> {
|
||||
editor.setText(content);
|
||||
editor.setSyntax(CodeSyntax.getFromFilename(fileName));
|
||||
editor.setOnContentChangedListener(this::onDocumentContentChanged);
|
||||
});
|
||||
}).start();
|
||||
|
||||
switch (args.type) {
|
||||
case LUA_SCRIPT_EDITOR:
|
||||
setupLuaPatchEditor();
|
||||
break;
|
||||
case READ_ONLY_EDITOR:
|
||||
setupReadOnlyEditor();
|
||||
break;
|
||||
}
|
||||
|
||||
onGlobalLayoutChanged();
|
||||
|
||||
findViewById(R.id.key_hide).setOnClickListener(v -> {
|
||||
((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
});
|
||||
findViewById(R.id.key_tab).setOnClickListener(v -> {
|
||||
editor.insert(TAB);
|
||||
});
|
||||
}
|
||||
|
||||
// Detect virtual keyboard is visible
|
||||
private void onGlobalLayoutChanged() {
|
||||
View view = getWindow().getDecorView();
|
||||
Rect rect = new Rect();
|
||||
view.getWindowVisibleDisplayFrame(rect);
|
||||
int currentHeight = rect.height();
|
||||
int height = view.getHeight();
|
||||
|
||||
if (currentHeight < height * 0.8) {
|
||||
findViewById(R.id.keybar).setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
findViewById(R.id.keybar).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupReadOnlyEditor() {
|
||||
editor.setEnabled(false);
|
||||
editor.setFocusable(false);
|
||||
}
|
||||
|
||||
private void setupLuaPatchEditor() {
|
||||
findViewById(R.id.lua_toolbar).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.lua_play).setOnClickListener(v -> {
|
||||
if (changed) {
|
||||
save();
|
||||
}
|
||||
setResult(Activity.RESULT_OK, new Intent(Result.ACTION_PLAY.name()));
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void onDocumentContentChanged() {
|
||||
changed = true;
|
||||
|
||||
title.setText("*" + fileName);
|
||||
saveButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
title.setText(fileName);
|
||||
saveButton.setVisibility(View.GONE);
|
||||
|
||||
changed = false;
|
||||
new Task(() -> FileUtils.writeTextFile(path, fileName, String.valueOf(editor.getText()))).runSync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
|
||||
if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
editor.insert(TAB);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (changed) {
|
||||
new BottomAlertDialog(this)
|
||||
.setNeutralButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
|
||||
.setPositiveButton(R.string.save_and_exit, (dialog, which) -> {
|
||||
save();
|
||||
finish();
|
||||
})
|
||||
.setNegativeButton(R.string.exit_without_saving, (dialog, which) -> finish())
|
||||
.setTitle(String.format(getString(R.string.exit_without_saving_title_ff), fileName)).show();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Arguments implements Serializable {
|
||||
private final String path;
|
||||
private final String fileName;
|
||||
private final EditorType type;
|
||||
|
||||
public Arguments(String path, String fileName, EditorType type) {
|
||||
this.path = path;
|
||||
this.fileName = fileName;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Result {
|
||||
ACTION_PLAY,
|
||||
NULL
|
||||
}
|
||||
|
||||
public enum EditorType {
|
||||
LUA_SCRIPT_EDITOR,
|
||||
READ_ONLY_EDITOR,
|
||||
TEXT_EDITOR
|
||||
}
|
||||
|
||||
public static final class Contract extends ActivityResultContract<Arguments, Result> {
|
||||
@NonNull
|
||||
@Override
|
||||
public Intent createIntent(@NonNull Context context, Arguments args) {
|
||||
return new Intent(context, CodeEditorActivity.class).putExtra("args", args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result parseResult(int i, @Nullable Intent intent) {
|
||||
return i == RESULT_OK && intent != null ? Result.valueOf(intent.getAction()) : Result.NULL;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,11 +47,15 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe
|
|||
((AppCompatTextView)view.findViewById(R.id.game_publisher)).setText(game.getPublisher());
|
||||
|
||||
((NavigationView)view.findViewById(R.id.action_navigation)).setNavigationItemSelectedListener(this);
|
||||
((NavigationView)view.findViewById(R.id.others_navigation)).setNavigationItemSelectedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
drawerContainer.removeDrawerListener(this);
|
||||
if (drawerContainer != null) {
|
||||
drawerContainer.removeDrawerListener(this);
|
||||
}
|
||||
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
|
@ -99,7 +103,9 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe
|
|||
if (id == R.id.resume) {
|
||||
close();
|
||||
} else if (id == R.id.exit) {
|
||||
requireActivity().onBackPressed();
|
||||
requireActivity().finish();
|
||||
} else if (id == R.id.lua_script){
|
||||
new LuaDialogFragment().show(getParentFragmentManager(), null);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
package com.panda3ds.pandroid.app.game;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.panda3ds.pandroid.AlberDriver;
|
||||
import com.panda3ds.pandroid.R;
|
||||
import com.panda3ds.pandroid.app.base.BottomAlertDialog;
|
||||
import com.panda3ds.pandroid.app.base.BottomDialogFragment;
|
||||
import com.panda3ds.pandroid.app.editor.CodeEditorActivity;
|
||||
import com.panda3ds.pandroid.lang.Task;
|
||||
import com.panda3ds.pandroid.utils.FileUtils;
|
||||
import com.panda3ds.pandroid.view.recycler.AutoFitGridLayout;
|
||||
import com.panda3ds.pandroid.view.recycler.SimpleListAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LuaDialogFragment extends BottomDialogFragment {
|
||||
private final SimpleListAdapter<LuaFile> adapter = new SimpleListAdapter<>(R.layout.holder_lua_script, this::onCreateListItem);
|
||||
private ActivityResultLauncher<CodeEditorActivity.Arguments> codeEditorLauncher;
|
||||
private LuaFile currentEditorFile;
|
||||
|
||||
private ActivityResultLauncher<String[]> openDocumentLauncher;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.dialog_lua_scripts, container, false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
openDocumentLauncher = registerForActivityResult(new ActivityResultContracts.OpenDocument(), result -> {
|
||||
if (result != null) {
|
||||
String fileName = FileUtils.getName(result.toString());
|
||||
|
||||
if (fileName.toLowerCase().endsWith(".lua")) {
|
||||
new Task(() -> {
|
||||
String content = FileUtils.readTextFile(result.toString());
|
||||
createFile(FileUtils.getName(result.toString()), content);
|
||||
}).start();
|
||||
} else {
|
||||
Toast.makeText(getContext(), R.string.file_not_supported, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
codeEditorLauncher = registerForActivityResult(new CodeEditorActivity.Contract(), result -> {
|
||||
if (result != null) {
|
||||
switch (result) {
|
||||
case ACTION_PLAY:
|
||||
loadScript(currentEditorFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
orderByModified();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
view.findViewById(R.id.open_file).setOnClickListener(v -> {
|
||||
openDocumentLauncher.launch(new String[]{"*/*"});
|
||||
});
|
||||
view.findViewById(R.id.create).setOnClickListener(v -> {
|
||||
new BottomAlertDialog(requireContext())
|
||||
.setTextInput(getString(R.string.name), arg -> {
|
||||
String name = arg.trim();
|
||||
if (name.length() > 1) {
|
||||
new Task(() -> {
|
||||
LuaFile file = createFile(name, "");
|
||||
currentEditorFile = file;
|
||||
codeEditorLauncher.launch(new CodeEditorActivity.Arguments(file.path, file.name, CodeEditorActivity.EditorType.LUA_SCRIPT_EDITOR));
|
||||
}).start();
|
||||
}
|
||||
}).setTitle(R.string.create_new)
|
||||
.show();
|
||||
});
|
||||
|
||||
((RecyclerView) view.findViewById(R.id.recycler)).setAdapter(adapter);
|
||||
((RecyclerView) view.findViewById(R.id.recycler)).setLayoutManager(new AutoFitGridLayout(getContext(), 140));
|
||||
FileUtils.createDir(FileUtils.getResourcesPath(), "Lua Scripts");
|
||||
ArrayList<LuaFile> files = new ArrayList<>();
|
||||
String path = FileUtils.getResourcesPath() + "/Lua Scripts/";
|
||||
for (String file : FileUtils.listFiles(path)) {
|
||||
files.add(new LuaFile(file));
|
||||
}
|
||||
|
||||
adapter.addAll(files);
|
||||
orderByModified();
|
||||
}
|
||||
|
||||
private LuaFile createFile(String name, String content) {
|
||||
if (name.toLowerCase().endsWith(".lua")) {
|
||||
name = name.substring(0, name.length() - 4);
|
||||
}
|
||||
|
||||
name = name.replaceAll("[^[a-zA-Z0-9-_ ]]", "-");
|
||||
|
||||
String fileName = name + "." + UUID.randomUUID().toString().substring(0, 4) + ".lua";
|
||||
LuaFile file = new LuaFile(fileName);
|
||||
FileUtils.writeTextFile(file.path, fileName, content);
|
||||
getView().post(() -> {
|
||||
adapter.addAll(file);
|
||||
orderByModified();
|
||||
});
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private void orderByModified() {
|
||||
adapter.sort((o1, o2) -> Long.compare(o2.lastModified(), o1.lastModified()));
|
||||
}
|
||||
|
||||
private void onCreateListItem(int position, LuaFile file, View view) {
|
||||
((TextView) view.findViewById(R.id.title))
|
||||
.setText(file.name.split("\\.")[0]);
|
||||
|
||||
view.setOnClickListener(v -> loadScript(file));
|
||||
view.findViewById(R.id.edit).setOnClickListener(v -> {
|
||||
currentEditorFile = file;
|
||||
codeEditorLauncher.launch(new CodeEditorActivity.Arguments(file.path, file.name, CodeEditorActivity.EditorType.LUA_SCRIPT_EDITOR));
|
||||
});
|
||||
}
|
||||
|
||||
private void loadScript(LuaFile file) {
|
||||
dismiss();
|
||||
|
||||
Toast.makeText(getContext(), String.format(getString(R.string.running_ff), file.name), Toast.LENGTH_SHORT).show();
|
||||
new Task(() -> {
|
||||
String script = FileUtils.readTextFile(file.absolutePath());
|
||||
file.update();
|
||||
AlberDriver.LoadLuaScript(script);
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
openDocumentLauncher.unregister();
|
||||
codeEditorLauncher.unregister();
|
||||
}
|
||||
|
||||
private static class LuaFile {
|
||||
private final String name;
|
||||
private final String path;
|
||||
|
||||
private LuaFile(String path, String name) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
private LuaFile(String name) {
|
||||
this(FileUtils.getResourcesPath() + "/Lua Scripts/", name);
|
||||
}
|
||||
|
||||
private String absolutePath() {
|
||||
return path + "/" + name;
|
||||
}
|
||||
|
||||
private void update() {
|
||||
FileUtils.updateFile(absolutePath());
|
||||
}
|
||||
|
||||
private long lastModified() {
|
||||
return FileUtils.getLastModified(absolutePath());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,6 +39,15 @@ public class FileUtils {
|
|||
return parseFile(path).getName();
|
||||
}
|
||||
|
||||
public static String getResourcesPath(){
|
||||
File file = new File(getPrivatePath(), "config/resources");
|
||||
if (!file.exists()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
public static String getPrivatePath() {
|
||||
File file = getContext().getFilesDir();
|
||||
if (!file.exists()) {
|
||||
|
@ -177,4 +186,42 @@ public class FileUtils {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateFile(String path){
|
||||
DocumentFile file = parseFile(path);
|
||||
Uri uri = file.getUri();
|
||||
|
||||
switch (uri.getScheme()) {
|
||||
case "file": {
|
||||
new File(uri.getPath()).setLastModified(System.currentTimeMillis());
|
||||
break;
|
||||
}
|
||||
|
||||
case "content": {
|
||||
getContext().getContentResolver().update(uri, null, null, null);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
Log.w(Constants.LOG_TAG, "Cannot update file from scheme: " + uri.getScheme());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static long getLastModified(String path) {
|
||||
return parseFile(path).lastModified();
|
||||
}
|
||||
|
||||
public static String[] listFiles(String path){
|
||||
DocumentFile folder = parseFile(path);
|
||||
DocumentFile[] files = folder.listFiles();
|
||||
|
||||
String[] result = new String[files.length];
|
||||
for (int i = 0; i < result.length; i++){
|
||||
result[i] = files[i].getName();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,322 @@
|
|||
package com.panda3ds.pandroid.view.code;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.text.Editable;
|
||||
import android.text.Layout;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class BaseEditor extends BasicTextEditor {
|
||||
private static final String HELLO_WORLD = "Hello World";
|
||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
|
||||
private final Rect rect = new Rect();
|
||||
private int currentLine;
|
||||
private float spaceWidth;
|
||||
private int lineHeight;
|
||||
private int textOffset;
|
||||
private int beginLine;
|
||||
private int beginIndex;
|
||||
private int endLine;
|
||||
private int endIndex;
|
||||
private int visibleHeight;
|
||||
private int contentWidth;
|
||||
private Layout textLayout;
|
||||
private int currentWidth = -1;
|
||||
private int currentHeight = -1;
|
||||
|
||||
private final char[] textBuffer = new char[1];
|
||||
protected final int[] colors = new int[256];
|
||||
|
||||
// Allocate 512KB for the buffer
|
||||
protected final byte[] syntaxBuffer = new byte[512 * 1024];
|
||||
private boolean requireUpdate = true;
|
||||
|
||||
public BaseEditor(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public BaseEditor(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public BaseEditor(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
{
|
||||
EditorColors.obtainColorScheme(colors, getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() {
|
||||
super.initialize();
|
||||
getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||
adjustScroll();
|
||||
requireUpdate = true;
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("MissingSuperCall")
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
//super.draw(canvas);
|
||||
canvas.drawColor(colors[EditorColors.COLOR_BACKGROUND]);
|
||||
textLayout = getLayout();
|
||||
if (textLayout == null) {
|
||||
postDelayed(this::invalidate, 25);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
prepareDraw();
|
||||
if (requireUpdate) {
|
||||
onVisibleContentChanged(beginIndex, endIndex - beginIndex);
|
||||
}
|
||||
|
||||
if (getSelectionStart() == getSelectionEnd()) {
|
||||
drawCaret(canvas);
|
||||
drawCurrentLine(canvas);
|
||||
} else {
|
||||
drawSelection(canvas);
|
||||
}
|
||||
|
||||
drawText(canvas);
|
||||
drawLineCount(canvas);
|
||||
} catch (Throwable e) {
|
||||
drawError(canvas, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawError(Canvas canvas, Throwable e) {
|
||||
canvas.drawColor(Color.RED);
|
||||
paint.setTextSize(getTextSize());
|
||||
paint.setColor(Color.WHITE);
|
||||
canvas.drawText("Editor draw error:", getPaddingLeft(), getLineHeight(), paint);
|
||||
canvas.drawText(String.valueOf(e), getPaddingLeft(), getLineHeight() * 2, paint);
|
||||
|
||||
int index = 2;
|
||||
for (StackTraceElement trace : e.getStackTrace()) {
|
||||
index++;
|
||||
if (index > 5) break;
|
||||
canvas.drawText(trace.getClassName() + ":" + trace.getMethodName() + ":" + trace.getLineNumber(), getPaddingLeft(), getLineHeight() * index, paint);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareDraw() {
|
||||
paint.setTypeface(getTypeface());
|
||||
paint.setTextSize(getTextSize());
|
||||
|
||||
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
|
||||
spaceWidth = paint.measureText(" ");
|
||||
lineHeight = getLineHeight();
|
||||
|
||||
//Align text to center of line
|
||||
{
|
||||
int ascent = (int) Math.abs(fontMetrics.ascent);
|
||||
paint.getTextBounds(HELLO_WORLD, 0, HELLO_WORLD.length(), rect);
|
||||
textOffset = Math.max(((lineHeight - rect.height()) / 2), 0) + ascent;
|
||||
}
|
||||
|
||||
int lineCount = textLayout.getLineCount();
|
||||
currentLine = textLayout.getLineForOffset(getSelectionStart());
|
||||
|
||||
int oldBeginLine = beginLine;
|
||||
int oldEndLine = endLine;
|
||||
|
||||
beginLine = Math.max(0, Math.min((getScrollY() / lineHeight) - 1, lineCount));
|
||||
beginIndex = textLayout.getLineStart(beginLine);
|
||||
|
||||
if (oldEndLine != endLine || beginLine != oldBeginLine) {
|
||||
requireUpdate = true;
|
||||
}
|
||||
|
||||
getGlobalVisibleRect(rect);
|
||||
visibleHeight = rect.height();
|
||||
|
||||
endLine = Math.round(((float) visibleHeight / lineHeight) + 2) + beginLine;
|
||||
endIndex = getLayout().getLineStart(Math.min(lineCount, endLine));
|
||||
|
||||
int padding = (int) (paint.measureText(String.valueOf(lineCount)) + (spaceWidth * 4));
|
||||
if (getPaddingLeft() != padding) {
|
||||
setPadding(padding, 0, 0, 0);
|
||||
}
|
||||
|
||||
contentWidth = getWidth() + getScrollX();
|
||||
}
|
||||
|
||||
private void drawLineCount(Canvas canvas) {
|
||||
int colorEnable = colors[EditorColors.COLOR_TEXT];
|
||||
int colorDisable = applyAlphaToColor(colors[EditorColors.COLOR_TEXT], 100);
|
||||
|
||||
paint.setColor(colors[EditorColors.COLOR_BACKGROUND_SECONDARY]);
|
||||
int scrollY = getScrollY();
|
||||
float x = getScrollX();
|
||||
|
||||
canvas.translate(x, 0);
|
||||
canvas.drawRect(0, scrollY, getPaddingLeft() - spaceWidth, visibleHeight + scrollY, paint);
|
||||
paint.setColor(colors[EditorColors.COLOR_CURRENT_LINE]);
|
||||
canvas.drawRect(0, currentLine * lineHeight, getPaddingLeft() - spaceWidth, (currentLine * lineHeight) + lineHeight, paint);
|
||||
|
||||
for (int i = beginLine; i < Math.min(getLineCount(), endLine); i++) {
|
||||
String text = String.valueOf(i + 1);
|
||||
if (i == currentLine) {
|
||||
paint.setColor(colorEnable);
|
||||
} else {
|
||||
paint.setColor(colorDisable);
|
||||
}
|
||||
|
||||
float width = paint.measureText(text);
|
||||
canvas.drawText(text, getPaddingLeft() - width - (spaceWidth * 2.5f), (i * lineHeight) + textOffset, paint);
|
||||
}
|
||||
|
||||
paint.setColor(applyAlphaToColor(colorEnable, 10));
|
||||
canvas.drawRect(getPaddingLeft() - spaceWidth - (spaceWidth / 4), scrollY, getPaddingLeft() - spaceWidth, visibleHeight + scrollY, paint);
|
||||
|
||||
canvas.translate(-x, 0);
|
||||
}
|
||||
|
||||
private void drawCurrentLine(Canvas canvas) {
|
||||
float y = currentLine * lineHeight;
|
||||
paint.setColor(colors[EditorColors.COLOR_CURRENT_LINE]);
|
||||
canvas.drawRect(0, y, contentWidth, y + lineHeight, paint);
|
||||
}
|
||||
|
||||
private void drawText(Canvas canvas) {
|
||||
Editable edit = getText();
|
||||
float x = 0;
|
||||
float y = textOffset;
|
||||
int line = 0;
|
||||
|
||||
canvas.translate(getPaddingLeft(), beginLine * lineHeight);
|
||||
|
||||
paint.setColor(colors[EditorColors.COLOR_TEXT]);
|
||||
for (int i = beginIndex; i < endIndex; i++) {
|
||||
textBuffer[0] = edit.charAt(i);
|
||||
switch (textBuffer[0]) {
|
||||
case '\n':
|
||||
line++;
|
||||
x = 0;
|
||||
y = (line * lineHeight) + textOffset;
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
x += spaceWidth;
|
||||
break;
|
||||
|
||||
default:
|
||||
paint.setColor(colors[syntaxBuffer[i - beginIndex]]);
|
||||
canvas.drawText(textBuffer, 0, 1, x, y, paint);
|
||||
x += paint.measureText(textBuffer, 0, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.translate(-getPaddingLeft(), -(beginLine * lineHeight));
|
||||
}
|
||||
|
||||
private void drawCaret(Canvas canvas) {
|
||||
int start = textLayout.getLineStart(currentLine);
|
||||
int end = textLayout.getLineEnd(currentLine);
|
||||
int position = getSelectionStart();
|
||||
float x = getPaddingLeft();
|
||||
float y = (currentLine * lineHeight);
|
||||
Editable text = getText();
|
||||
for (int i = start; i < end; i++) {
|
||||
if (i == position) {
|
||||
break;
|
||||
}
|
||||
|
||||
textBuffer[0] = text.charAt(i);
|
||||
x += paint.measureText(textBuffer, 0, 1);
|
||||
}
|
||||
|
||||
paint.setColor(colors[EditorColors.COLOR_CARET]);
|
||||
float caretWidth = spaceWidth / 2;
|
||||
canvas.drawRect(x - (caretWidth / 2), y, x + (caretWidth / 2), y + lineHeight, paint);
|
||||
}
|
||||
|
||||
private void drawSelection(Canvas canvas) {
|
||||
int start = getSelectionStart();
|
||||
int end = getSelectionEnd();
|
||||
int endLine = textLayout.getLineForOffset(end);
|
||||
canvas.translate(getPaddingLeft(), 0);
|
||||
|
||||
paint.setColor(colors[EditorColors.COLOR_SELECTION]);
|
||||
|
||||
Editable text = getText();
|
||||
|
||||
for (int line = currentLine; line <= endLine; line++) {
|
||||
|
||||
if (line < beginLine) continue;
|
||||
if (line > this.endLine) break;
|
||||
|
||||
if (line == endLine || line == currentLine) {
|
||||
int lineStart = textLayout.getLineStart(line);
|
||||
float x = 0;
|
||||
|
||||
if (lineStart <= start) {
|
||||
x = paint.measureText(text, lineStart, start);
|
||||
lineStart = start;
|
||||
}
|
||||
float width;
|
||||
if (line < endLine) {
|
||||
width = contentWidth;
|
||||
} else {
|
||||
width = paint.measureText(text, lineStart, end);
|
||||
}
|
||||
|
||||
canvas.drawRect(x, lineHeight * line, x + width, (lineHeight * line) + lineHeight, paint);
|
||||
} else {
|
||||
canvas.drawRect(0, lineHeight * line, contentWidth, (lineHeight * line) + lineHeight, paint);
|
||||
}
|
||||
}
|
||||
canvas.translate(-getPaddingLeft(), 0);
|
||||
}
|
||||
|
||||
public int applyAlphaToColor(int color, int alpha) {
|
||||
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
|
||||
protected void onVisibleContentChanged(int index, int length) {
|
||||
requireUpdate = false;
|
||||
|
||||
Arrays.fill(syntaxBuffer, (byte) 0);
|
||||
if (length > 0) {
|
||||
onRefreshColorScheme(syntaxBuffer, index, length);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onRefreshColorScheme(byte[] buffer, int index, int length) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (currentWidth != getMeasuredWidth() || currentHeight != getMeasuredHeight()) {
|
||||
currentWidth = getMeasuredWidth();
|
||||
currentHeight = getMeasuredHeight();
|
||||
invalidateAll();
|
||||
}
|
||||
}
|
||||
|
||||
protected void invalidateAll() {
|
||||
requireUpdate = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTextChanged() {
|
||||
requireUpdate = true;
|
||||
super.onTextChanged();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package com.panda3ds.pandroid.view.code;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Scroller;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
|
||||
import com.panda3ds.pandroid.view.SimpleTextWatcher;
|
||||
|
||||
public class BasicTextEditor extends AppCompatEditText {
|
||||
private GestureDetector gestureDetector;
|
||||
private final Rect visibleRect = new Rect();
|
||||
|
||||
public BasicTextEditor(@NonNull Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public BasicTextEditor(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public BasicTextEditor(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize();
|
||||
}
|
||||
|
||||
protected void initialize() {
|
||||
setTypeface(Typeface.MONOSPACE);
|
||||
gestureDetector = new GestureDetector(getContext(), new ScrollGesture());
|
||||
|
||||
setTypeface(Typeface.createFromAsset(getContext().getAssets(), "fonts/comic_mono.ttf"));
|
||||
setGravity(Gravity.START | Gravity.TOP);
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
|
||||
setLineSpacing(0, 1.3f);
|
||||
setScroller(new Scroller(getContext()));
|
||||
|
||||
setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS |
|
||||
InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE |
|
||||
InputType.TYPE_TEXT_FLAG_MULTI_LINE |
|
||||
InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
|
||||
|
||||
setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
setBackgroundColor(Color.BLACK);
|
||||
setTextColor(Color.WHITE);
|
||||
|
||||
setFocusableInTouchMode(true);
|
||||
setHorizontallyScrolling(true);
|
||||
setHorizontalScrollBarEnabled(true);
|
||||
|
||||
addTextChangedListener((SimpleTextWatcher) value -> BasicTextEditor.this.onTextChanged());
|
||||
}
|
||||
|
||||
// Disable default Android scroll
|
||||
@Override
|
||||
public void scrollBy(int x, int y) {}
|
||||
|
||||
@Override
|
||||
public void scrollTo(int x, int y) {}
|
||||
|
||||
public void setScroll(int x, int y) {
|
||||
x = Math.max(0, x);
|
||||
y = Math.max(0, y);
|
||||
|
||||
int maxHeight = Math.round(getLineCount() * getLineHeight());
|
||||
getGlobalVisibleRect(visibleRect);
|
||||
maxHeight = Math.max(0, maxHeight - visibleRect.height());
|
||||
|
||||
int maxWidth = (int) getPaint().measureText(getText(), 0, length());
|
||||
maxWidth += getPaddingLeft() + getPaddingRight();
|
||||
|
||||
int scrollX = x - Math.max(Math.min(maxWidth - visibleRect.width(), x), 0);
|
||||
int scrollY = Math.min(maxHeight, y);
|
||||
|
||||
super.scrollTo(scrollX, scrollY);
|
||||
}
|
||||
|
||||
public void adjustScroll(){
|
||||
setScroll(getScrollX(), getScrollY());
|
||||
}
|
||||
|
||||
protected void onTextChanged() {}
|
||||
|
||||
private boolean onSuperTouchListener(MotionEvent event) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return gestureDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
private class ScrollGesture implements GestureDetector.OnGestureListener {
|
||||
@Override
|
||||
public boolean onDown(@NonNull MotionEvent e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(@NonNull MotionEvent e) {
|
||||
onSuperTouchListener(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(@NonNull MotionEvent e) {
|
||||
return onSuperTouchListener(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
|
||||
int scrollX = (int) Math.max(0, getScrollX() + distanceX);
|
||||
int scrollY = (int) Math.max(0, getScrollY() + distanceY);
|
||||
setScroll(scrollX, scrollY);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(@NonNull MotionEvent e) {
|
||||
onSuperTouchListener(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void insert(CharSequence text) {
|
||||
if (getSelectionStart() == getSelectionEnd()) {
|
||||
getText().insert(getSelectionStart(), text);
|
||||
} else {
|
||||
getText().replace(getSelectionStart(), getSelectionEnd(), text);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package com.panda3ds.pandroid.view.code;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.panda3ds.pandroid.view.code.syntax.CodeSyntax;
|
||||
|
||||
public class CodeEditor extends BaseEditor {
|
||||
private CodeSyntax syntax;
|
||||
private Runnable contentChangeListener;
|
||||
|
||||
public CodeEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CodeEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CodeEditor(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setSyntax(CodeSyntax syntax) {
|
||||
this.syntax = syntax;
|
||||
invalidateAll();
|
||||
}
|
||||
|
||||
public void setOnContentChangedListener(Runnable contentChangeListener) {
|
||||
this.contentChangeListener = contentChangeListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTextChanged() {
|
||||
super.onTextChanged();
|
||||
if (contentChangeListener != null) {
|
||||
contentChangeListener.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRefreshColorScheme(byte[] buffer, int index, int length) {
|
||||
super.onRefreshColorScheme(buffer, index, length);
|
||||
|
||||
if (syntax != null) {
|
||||
final CharSequence text = getText().subSequence(index, index + length);
|
||||
syntax.apply(syntaxBuffer, text);
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package com.panda3ds.pandroid.view.code;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.panda3ds.pandroid.app.PandroidApplication;
|
||||
|
||||
public class EditorColors {
|
||||
public static final byte COLOR_TEXT = 0x0;
|
||||
public static final byte COLOR_KEYWORDS = 0x1;
|
||||
public static final byte COLOR_NUMBERS = 0x2;
|
||||
public static final byte COLOR_STRING = 0x3;
|
||||
public static final byte COLOR_METADATA = 0x4;
|
||||
public static final byte COLOR_COMMENT = 0x5;
|
||||
public static final byte COLOR_SYMBOLS = 0x6;
|
||||
public static final byte COLOR_FIELDS = 0x7;
|
||||
public static final byte COLOR_BACKGROUND = 0x1D;
|
||||
public static final byte COLOR_BACKGROUND_SECONDARY = 0x2D;
|
||||
public static final byte COLOR_SELECTION = 0x3D;
|
||||
public static final byte COLOR_CARET = 0x4D;
|
||||
public static final byte COLOR_CURRENT_LINE = 0x5D;
|
||||
|
||||
public static void obtainColorScheme(int[] colors, Context context) {
|
||||
if (PandroidApplication.isDarkMode()) {
|
||||
applyDarkTheme(colors);
|
||||
} else {
|
||||
applyLightTheme(colors);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyLightTheme(int[] colors) {
|
||||
colors[EditorColors.COLOR_TEXT] = 0xFF000000;
|
||||
colors[EditorColors.COLOR_KEYWORDS] = 0xFF3AE666;
|
||||
colors[EditorColors.COLOR_NUMBERS] = 0xFF3A9EE6;
|
||||
colors[EditorColors.COLOR_METADATA] = 0xFF806AE6;
|
||||
colors[EditorColors.COLOR_SYMBOLS] = 0xFF202020;
|
||||
colors[EditorColors.COLOR_STRING] = 0xFF2EB541;
|
||||
colors[EditorColors.COLOR_FIELDS] = 0xFF9876AA;
|
||||
colors[EditorColors.COLOR_COMMENT] = 0xFF808080;
|
||||
|
||||
colors[EditorColors.COLOR_BACKGROUND] = 0xFFFFFFFF;
|
||||
colors[EditorColors.COLOR_BACKGROUND_SECONDARY] = 0xFFF0F0F0;
|
||||
colors[EditorColors.COLOR_SELECTION] = 0x701F9EDE;
|
||||
colors[EditorColors.COLOR_CARET] = 0xFF000000;
|
||||
colors[EditorColors.COLOR_CURRENT_LINE] = 0x05000050;
|
||||
}
|
||||
|
||||
private static void applyDarkTheme(int[] colors) {
|
||||
colors[EditorColors.COLOR_TEXT] = 0xFFFFFFFF;
|
||||
colors[EditorColors.COLOR_KEYWORDS] = 0xFFE37F3E;
|
||||
colors[EditorColors.COLOR_NUMBERS] = 0xFF3A9EE6;
|
||||
colors[EditorColors.COLOR_METADATA] = 0xFFC5CA1D;
|
||||
colors[EditorColors.COLOR_SYMBOLS] = 0xFFC0C0C0;
|
||||
colors[EditorColors.COLOR_STRING] = 0xFF2EB541;
|
||||
colors[EditorColors.COLOR_FIELDS] = 0xFF9876AA;
|
||||
colors[EditorColors.COLOR_COMMENT] = 0xFFBBBBBB;
|
||||
|
||||
colors[EditorColors.COLOR_BACKGROUND] = 0xFF2B2B2B;
|
||||
colors[EditorColors.COLOR_BACKGROUND_SECONDARY] = 0xFF313335;
|
||||
colors[EditorColors.COLOR_SELECTION] = 0x701F9EDE;
|
||||
colors[EditorColors.COLOR_CARET] = 0x60FFFFFF;
|
||||
colors[EditorColors.COLOR_CURRENT_LINE] = 0x10FFFFFF;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.panda3ds.pandroid.view.code.syntax;
|
||||
|
||||
public abstract class CodeSyntax {
|
||||
public abstract void apply(byte[] syntaxBuffer, final CharSequence text);
|
||||
|
||||
// Get syntax highlighting data for a file based on its filename, by looking at the extension
|
||||
public static CodeSyntax getFromFilename(String name) {
|
||||
name = name.trim().toLowerCase();
|
||||
String[] parts = name.split("\\.");
|
||||
if (parts.length == 0)
|
||||
return null;
|
||||
|
||||
// Get syntax based on file extension
|
||||
switch (parts[parts.length - 1]) {
|
||||
case "lua":
|
||||
return new LuaSyntax();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.panda3ds.pandroid.view.code.syntax;
|
||||
|
||||
import com.panda3ds.pandroid.view.code.EditorColors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class LuaSyntax extends CodeSyntax {
|
||||
public static final Pattern comment = Pattern.compile("(\\-\\-.*)");
|
||||
|
||||
public static final Pattern keywords = PatternUtils.buildGenericKeywords(
|
||||
"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in",
|
||||
"local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while");
|
||||
|
||||
public static final Pattern identifiers = PatternUtils.buildGenericKeywords(
|
||||
"assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset",
|
||||
"select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION", "arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace",
|
||||
"rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug", "getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable",
|
||||
"getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen",
|
||||
"read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger",
|
||||
"floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh",
|
||||
"pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock",
|
||||
"date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep",
|
||||
"reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern",
|
||||
"coroutine", "table", "io", "os", "string", "uint8_t", "bit32", "math", "debug", "package");
|
||||
|
||||
public static final Pattern string = Pattern.compile("((\")(.*?)([^\\\\]\"))|((\")(.+))|((')(.?)('))");
|
||||
public static final Pattern symbols = Pattern.compile("([.!&?:;*+/{}()\\]\\[,=-])");
|
||||
public static final Pattern numbers = Pattern.compile("\\b((\\d*[.]?\\d+([Ee][+-]?[\\d]+)?[LlfFdD]?)|(0[xX][0-9a-zA-Z]+)|(0[bB][0-1]+)|(0[0-7]+))\\b");
|
||||
|
||||
@Override
|
||||
public void apply(byte[] syntaxBuffer, CharSequence text) {
|
||||
for (Matcher matcher = keywords.matcher(text); matcher.find(); ) {
|
||||
Arrays.fill(syntaxBuffer, matcher.start(), matcher.end(), EditorColors.COLOR_KEYWORDS);
|
||||
}
|
||||
|
||||
for (Matcher matcher = identifiers.matcher(text); matcher.find(); ) {
|
||||
Arrays.fill(syntaxBuffer, matcher.start(), matcher.end(), EditorColors.COLOR_FIELDS);
|
||||
}
|
||||
|
||||
for (Matcher matcher = symbols.matcher(text); matcher.find(); ) {
|
||||
Arrays.fill(syntaxBuffer, matcher.start(), matcher.end(), EditorColors.COLOR_SYMBOLS);
|
||||
}
|
||||
|
||||
for (Matcher matcher = numbers.matcher(text); matcher.find(); ) {
|
||||
Arrays.fill(syntaxBuffer, matcher.start(), matcher.end(), EditorColors.COLOR_NUMBERS);
|
||||
}
|
||||
|
||||
for (Matcher matcher = string.matcher(text); matcher.find(); ) {
|
||||
Arrays.fill(syntaxBuffer, matcher.start(), matcher.end(), EditorColors.COLOR_STRING);
|
||||
}
|
||||
|
||||
for (Matcher matcher = comment.matcher(text); matcher.find(); ) {
|
||||
Arrays.fill(syntaxBuffer, matcher.start(), matcher.end(), EditorColors.COLOR_COMMENT);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.panda3ds.pandroid.view.code.syntax;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class PatternUtils {
|
||||
public static Pattern buildGenericKeywords(String... keywords){
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("\\b(");
|
||||
for (int i = 0; i < keywords.length; i++){
|
||||
builder.append(keywords[i]);
|
||||
if (i+1 != keywords.length){
|
||||
builder.append("|");
|
||||
}
|
||||
}
|
||||
builder.append(")\\b");
|
||||
return Pattern.compile(builder.toString());
|
||||
}
|
||||
}
|
|
@ -5,15 +5,14 @@ import android.util.AttributeSet;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.panda3ds.pandroid.data.game.GameMetadata;
|
||||
import com.panda3ds.pandroid.view.recycler.AutoFitGridLayout;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GamesGridView extends RecyclerView {
|
||||
private int iconSize = 170;
|
||||
private final GameAdapter adapter;
|
||||
|
||||
public GamesGridView(@NonNull Context context) {
|
||||
|
@ -26,33 +25,11 @@ public class GamesGridView extends RecyclerView {
|
|||
|
||||
public GamesGridView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setLayoutManager(new AutoFitLayout());
|
||||
setLayoutManager(new AutoFitGridLayout(getContext(), 170));
|
||||
setAdapter(adapter = new GameAdapter());
|
||||
}
|
||||
|
||||
public void setGameList(List<GameMetadata> games) {
|
||||
adapter.replace(games);
|
||||
}
|
||||
|
||||
public void setIconSize(int iconSize) {
|
||||
this.iconSize = iconSize;
|
||||
requestLayout();
|
||||
measure(MeasureSpec.EXACTLY, MeasureSpec.EXACTLY);
|
||||
}
|
||||
|
||||
private final class AutoFitLayout extends GridLayoutManager {
|
||||
public AutoFitLayout() {
|
||||
super(GamesGridView.this.getContext(), 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec, int heightSpec) {
|
||||
super.onMeasure(recycler, state, widthSpec, heightSpec);
|
||||
int width = getMeasuredWidth();
|
||||
int iconSize = (int) (GamesGridView.this.iconSize * getResources().getDisplayMetrics().density);
|
||||
int iconCount = Math.max(1, width / iconSize);
|
||||
if (getSpanCount() != iconCount)
|
||||
setSpanCount(iconCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.panda3ds.pandroid.view.recycler;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public final class AutoFitGridLayout extends GridLayoutManager {
|
||||
private final int iconSize;
|
||||
private final Context context;
|
||||
|
||||
public AutoFitGridLayout(Context context, int iconSize) {
|
||||
super(context, 1);
|
||||
|
||||
this.iconSize = iconSize;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeasure(@NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state, int widthSpec, int heightSpec) {
|
||||
super.onMeasure(recycler, state, widthSpec, heightSpec);
|
||||
int width = View.MeasureSpec.getSize(widthSpec);
|
||||
int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.iconSize, context.getResources().getDisplayMetrics());
|
||||
int iconCount = Math.max(1, width / iconSize);
|
||||
|
||||
if (getSpanCount() != iconCount) {
|
||||
setSpanCount(iconCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package com.panda3ds.pandroid.view.recycler;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class SimpleListAdapter<T> extends RecyclerView.Adapter<SimpleListAdapter.Holder> {
|
||||
private final ArrayList<T> list = new ArrayList<>();
|
||||
private final Binder<T> binder;
|
||||
private final int layoutId;
|
||||
|
||||
public SimpleListAdapter(@LayoutRes int layoutId, Binder<T> binder) {
|
||||
this.layoutId = layoutId;
|
||||
this.binder = binder;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new Holder(LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull Holder holder, int position) {
|
||||
binder.bind(position, list.get(position), holder.getView());
|
||||
}
|
||||
|
||||
public void addAll(T... items) {
|
||||
addAll(Arrays.asList(items));
|
||||
}
|
||||
|
||||
public void addAll(List<T> items) {
|
||||
int index = list.size();
|
||||
this.list.addAll(items);
|
||||
notifyItemRangeInserted(index, getItemCount() - index);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
int count = getItemCount();
|
||||
list.clear();
|
||||
notifyItemRangeRemoved(0, count);
|
||||
}
|
||||
|
||||
public void sort(Comparator<T> comparator) {
|
||||
list.sort(comparator);
|
||||
notifyItemRangeChanged(0, getItemCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
public interface Binder<I> {
|
||||
void bind(int position, I item, View view);
|
||||
}
|
||||
|
||||
public static class Holder extends RecyclerView.ViewHolder {
|
||||
public Holder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return itemView;
|
||||
}
|
||||
}
|
||||
}
|
5
src/pandroid/app/src/main/res/drawable/ic_code.xml
Normal file
5
src/pandroid/app/src/main/res/drawable/ic_code.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#000000"
|
||||
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="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"/>
|
||||
</vector>
|
5
src/pandroid/app/src/main/res/drawable/ic_edit.xml
Normal file
5
src/pandroid/app/src/main/res/drawable/ic_edit.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#000000"
|
||||
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"/>
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#000000"
|
||||
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="M20,3L4,3c-1.1,0 -1.99,0.9 -1.99,2L2,15c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM11,6h2v2h-2L11,6zM11,9h2v2h-2L11,9zM8,6h2v2L8,8L8,6zM8,9h2v2L8,11L8,9zM7,11L5,11L5,9h2v2zM7,8L5,8L5,6h2v2zM16,15L8,15v-2h8v2zM16,11h-2L14,9h2v2zM16,8h-2L14,6h2v2zM19,11h-2L17,9h2v2zM19,8h-2L17,6h2v2zM12,23l4,-4L8,19l4,4z"/>
|
||||
</vector>
|
5
src/pandroid/app/src/main/res/drawable/ic_play.xml
Normal file
5
src/pandroid/app/src/main/res/drawable/ic_play.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#000000"
|
||||
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="M8,6.82v10.36c0,0.79 0.87,1.27 1.54,0.84l8.14,-5.18c0.62,-0.39 0.62,-1.29 0,-1.69L9.54,5.98C8.87,5.55 8,6.03 8,6.82z"/>
|
||||
</vector>
|
5
src/pandroid/app/src/main/res/drawable/ic_tab.xml
Normal file
5
src/pandroid/app/src/main/res/drawable/ic_tab.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" 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="M11.59,7.41L15.17,11H1v2h14.17l-3.59,3.59L13,18l6,-6 -6,-6 -1.41,1.41zM20,6v12h2V6h-2z"/>
|
||||
</vector>
|
133
src/pandroid/app/src/main/res/layout/activity_code_editor.xml
Normal file
133
src/pandroid/app/src/main/res/layout/activity_code_editor.xml
Normal file
|
@ -0,0 +1,133 @@
|
|||
<?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"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="58dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="start|center"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="17sp"/>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end|center"
|
||||
android:paddingLeft="14dp"
|
||||
android:paddingRight="14dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/save"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:padding="5dp"
|
||||
android:tint="?colorOnSurface"
|
||||
app:srcCompat="@drawable/ic_save"
|
||||
android:scaleType="centerInside"
|
||||
android:background="#0000"/>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/lua_toolbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/lua_play"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:padding="5dp"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:tint="?colorOnSurface"
|
||||
app:srcCompat="@drawable/ic_play"
|
||||
android:scaleType="centerInside"
|
||||
android:background="#0000"/>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?colorOnSurface"
|
||||
android:alpha="0.1"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.panda3ds.pandroid.view.code.CodeEditor
|
||||
android:id="@+id/editor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/keybar"/>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/keybar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHeight="38dp"
|
||||
android:visibility="visible"
|
||||
android:orientation="horizontal"
|
||||
android:background="?colorSurfaceVariant">
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/key_tab"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_tab"
|
||||
android:tint="?colorOnSurfaceVariant"
|
||||
android:background="#0000"/>
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/key_hide"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_keyboard_hide"
|
||||
android:tint="?colorOnSurfaceVariant"
|
||||
android:background="#0000"/>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</FrameLayout>
|
65
src/pandroid/app/src/main/res/layout/dialog_lua_scripts.xml
Normal file
65
src/pandroid/app/src/main/res/layout/dialog_lua_scripts.xml
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="100dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintHeight_max="400dp"
|
||||
app:layout_constraintHeight_min="200dp"
|
||||
tools:itemCount="5"
|
||||
tools:listitem="@layout/holder_lua_script"
|
||||
android:layout_marginBottom="10dp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="bottom|center">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/open_file"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/open_file"
|
||||
android:layout_margin="5dp"
|
||||
android:textColor="?colorOnPrimary"
|
||||
android:backgroundTint="?colorPrimary"
|
||||
android:background="@drawable/simple_card_background"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/create"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:text="@string/create_new"
|
||||
android:layout_margin="5dp"
|
||||
android:textColor="?colorOnSurfaceVariant"
|
||||
android:backgroundTint="?colorSurfaceVariant"
|
||||
android:background="@drawable/simple_card_background"
|
||||
android:layout_marginBottom="10dp"/>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
@ -85,6 +85,19 @@
|
|||
app:menu="@menu/game_drawer_actions"
|
||||
android:background="?colorSurface"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/others"
|
||||
style="@style/TextAppearanceGameDrawerSubTitle"/>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/others_navigation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:menu="@menu/game_drawer_others"
|
||||
android:background="?colorSurface"/>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</ScrollView>
|
||||
|
|
52
src/pandroid/app/src/main/res/layout/holder_lua_script.xml
Normal file
52
src/pandroid/app/src/main/res/layout/holder_lua_script.xml
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?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="wrap_content"
|
||||
android:gravity="start|center"
|
||||
android:padding="5dp">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/simple_card_background"
|
||||
android:backgroundTint="?colorSurfaceVariant"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="10dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:tint="?colorOnSurfaceVariant"
|
||||
android:alpha="0.5"
|
||||
android:padding="14dp"
|
||||
android:src="@drawable/ic_code"
|
||||
android:layout_marginBottom="14dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"/>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/edit"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@drawable/ic_edit"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="10dp"
|
||||
android:tint="?colorOnSurfaceVariant"
|
||||
android:layout_gravity="end|top"
|
||||
android:background="#0000"
|
||||
android:layout_margin="10dp"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/lua_script"
|
||||
android:icon="@drawable/ic_code"
|
||||
android:title="@string/lua_script"/>
|
||||
</menu>
|
|
@ -35,4 +35,14 @@
|
|||
<string name="pref_screen_controllers_title">Disposições de controle</string>
|
||||
<string name="pref_default_controller_title">Disposição de controle padrão</string>
|
||||
<string name="invalid_name">Nome Invalido</string>
|
||||
<string name="hacks">Trapaças</string>
|
||||
<string name="lua_script">Script Lua</string>
|
||||
<string name="scripts">Scripts</string>
|
||||
<string name="file_not_supported">Esse arquivo não é suportado</string>
|
||||
<string name="save_and_exit">Salvar e sair</string>
|
||||
<string name="exit_without_saving">Sair sem salvar</string>
|
||||
<string name="exit_without_saving_title_ff">Salvar \"%s\" antes de sair?</string>
|
||||
<string name="open_file">Abrir arquivo</string>
|
||||
<string name="create_new">Criar novo</string>
|
||||
<string name="running_ff">Executando \"%s\" ...</string>
|
||||
</resources>
|
|
@ -36,4 +36,14 @@
|
|||
<string name="pref_screen_controllers_title">Screen gamepad layouts</string>
|
||||
<string name="pref_default_controller_title">Default screen gamepad layout</string>
|
||||
<string name="invalid_name">Invalid name</string>
|
||||
<string name="hacks">Hacks</string>
|
||||
<string name="lua_script">Lua script</string>
|
||||
<string name="scripts">Scripts</string>
|
||||
<string name="file_not_supported">File type isn\'t supported</string>
|
||||
<string name="save_and_exit">Save and exit</string>
|
||||
<string name="exit_without_saving">Exit without saving</string>
|
||||
<string name="exit_without_saving_title_ff">Exit without saving \"%s\"?</string>
|
||||
<string name="open_file">Open file</string>
|
||||
<string name="create_new">Create new</string>
|
||||
<string name="running_ff">Running \"%s\" ...</string>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Reference in a new issue