mirror of
https://github.com/2009scape/2009Scape-mobile.git
synced 2025-12-10 10:20:32 -07:00
ignore
This commit is contained in:
parent
f4ac5b8e50
commit
bdb7c4fa54
90 changed files with 4 additions and 9876 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1,8 +1,9 @@
|
|||
/.gradle
|
||||
/app_2009scape/.cxx/
|
||||
app_2009scape/.cxx/
|
||||
/build
|
||||
/*/build
|
||||
app_pojavlauncher/src/main/assets/components/jre
|
||||
app_2009scape/src/main/assets/components/jre
|
||||
local.properties
|
||||
.idea/
|
||||
app_pojavlauncher/.cxx/
|
||||
.vs/
|
||||
|
|
@ -1,240 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
|
||||
import androidx.appcompat.app.*;
|
||||
import androidx.preference.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.kdt.pickafile.*;
|
||||
import java.io.*;
|
||||
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.*;
|
||||
|
||||
|
||||
|
||||
public class CustomControlsActivity extends BaseActivity
|
||||
{
|
||||
private DrawerLayout drawerLayout;
|
||||
private NavigationView navDrawer;
|
||||
private ControlLayout ctrlLayout;
|
||||
|
||||
private SharedPreferences mPref;
|
||||
|
||||
public boolean isModified = false;
|
||||
public boolean isFromMainActivity = false;
|
||||
private static String selectedName = "new_control";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (getIntent().getExtras() != null && getIntent().getExtras().getBoolean("fromMainActivity", false)) {
|
||||
// TODO translucent!
|
||||
// setTheme(androidx.appcompat.R.style.Theme_AppCompat_Translucent);
|
||||
}
|
||||
|
||||
setContentView(R.layout.control_mapping);
|
||||
|
||||
mPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
ctrlLayout = (ControlLayout) findViewById(R.id.customctrl_controllayout);
|
||||
|
||||
// Menu
|
||||
drawerLayout = (DrawerLayout) findViewById(R.id.customctrl_drawerlayout);
|
||||
|
||||
navDrawer = (NavigationView) findViewById(R.id.customctrl_navigation_view);
|
||||
navDrawer.setNavigationItemSelectedListener(
|
||||
new NavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(MenuItem menuItem) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_ctrl_load:
|
||||
load(ctrlLayout);
|
||||
break;
|
||||
case R.id.menu_ctrl_add:
|
||||
ctrlLayout.addControlButton(new ControlData("New"));
|
||||
break;
|
||||
case R.id.menu_ctrl_add_drawer:
|
||||
ctrlLayout.addDrawer(new ControlDrawerData());
|
||||
break;
|
||||
case R.id.menu_ctrl_selectdefault:
|
||||
dialogSelectDefaultCtrl(ctrlLayout);
|
||||
break;
|
||||
case R.id.menu_ctrl_save:
|
||||
save(false,ctrlLayout);
|
||||
break;
|
||||
}
|
||||
//Toast.makeText(MainActivity.this, menuItem.getTitle() + ":" + menuItem.getItemId(), Toast.LENGTH_SHORT).show();
|
||||
|
||||
drawerLayout.closeDrawers();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
ctrlLayout.setActivity(this);
|
||||
ctrlLayout.setModifiable(true);
|
||||
|
||||
loadControl(LauncherPreferences.PREF_DEFAULTCTRL_PATH,ctrlLayout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!isModified) {
|
||||
setResult(Activity.RESULT_OK, new Intent());
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
save(true,ctrlLayout);
|
||||
}
|
||||
|
||||
private static void setDefaultControlJson(String path,ControlLayout ctrlLayout) {
|
||||
try {
|
||||
// Load before save to make sure control is not error
|
||||
ctrlLayout.loadLayout(Tools.GLOBAL_GSON.fromJson(Tools.read(path), CustomControls.class));
|
||||
LauncherPreferences.DEFAULT_PREF.edit().putString("defaultCtrl", path).commit();
|
||||
LauncherPreferences.PREF_DEFAULTCTRL_PATH = path;
|
||||
} catch (Throwable th) {
|
||||
Tools.showError(ctrlLayout.getContext(), th);
|
||||
}
|
||||
}
|
||||
|
||||
public static void dialogSelectDefaultCtrl(final ControlLayout layout) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(layout.getContext());
|
||||
builder.setTitle(R.string.customctrl_selectdefault);
|
||||
builder.setPositiveButton(android.R.string.cancel, null);
|
||||
|
||||
final AlertDialog dialog = builder.create();
|
||||
FileListView flv = new FileListView(dialog, "json");
|
||||
flv.lockPathAt(Tools.CTRLMAP_PATH);
|
||||
flv.setFileSelectedListener(new FileSelectedListener(){
|
||||
|
||||
@Override
|
||||
public void onFileSelected(File file, String path) {
|
||||
setDefaultControlJson(path,layout);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
dialog.setView(flv);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private static String doSaveCtrl(String name, final ControlLayout layout) throws Exception {
|
||||
String jsonPath = Tools.CTRLMAP_PATH + "/" + name + ".json";
|
||||
layout.saveLayout(jsonPath);
|
||||
|
||||
return jsonPath;
|
||||
}
|
||||
|
||||
public static void save(final boolean exit, final ControlLayout layout) {
|
||||
final Context ctx = layout.getContext();
|
||||
final EditText edit = new EditText(ctx);
|
||||
edit.setSingleLine();
|
||||
edit.setText(selectedName);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(R.string.global_save);
|
||||
builder.setView(edit);
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
if (exit) {
|
||||
builder.setNeutralButton(R.string.mcn_exit_call, new AlertDialog.OnClickListener(){
|
||||
@Override
|
||||
public void onClick(DialogInterface p1, int p2) {
|
||||
layout.setModifiable(false);
|
||||
if(ctx instanceof MainActivity) {
|
||||
((MainActivity) ctx).leaveCustomControls();
|
||||
}else{
|
||||
((CustomControlsActivity) ctx).isModified = false;
|
||||
((Activity)ctx).onBackPressed();
|
||||
}
|
||||
// setResult(Activity.RESULT_OK, new Intent());
|
||||
// CustomControlsActivity.super.onBackPressed();
|
||||
}
|
||||
});
|
||||
}
|
||||
final AlertDialog dialog = builder.create();
|
||||
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
|
||||
@Override
|
||||
public void onShow(DialogInterface dialogInterface) {
|
||||
|
||||
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (edit.getText().toString().isEmpty()) {
|
||||
edit.setError(ctx.getResources().getString(R.string.global_error_field_empty));
|
||||
} else {
|
||||
try {
|
||||
String jsonPath = doSaveCtrl(edit.getText().toString(),layout);
|
||||
|
||||
Toast.makeText(ctx, ctx.getString(R.string.global_save) + ": " + jsonPath, Toast.LENGTH_SHORT).show();
|
||||
|
||||
dialog.dismiss();
|
||||
if (exit) {
|
||||
if(ctx instanceof MainActivity) {
|
||||
((MainActivity) ctx).leaveCustomControls();
|
||||
}else{
|
||||
((Activity)ctx).onBackPressed();
|
||||
}
|
||||
//CustomControlsActivity.super.onBackPressed();
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Tools.showError(ctx, th, exit);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
|
||||
}
|
||||
|
||||
public static void load(final ControlLayout layout) {
|
||||
/*ControlJsonSelector sel = new ControlJsonSelector(layout.getContext(), R.string.global_load);
|
||||
sel.setFinishCallback((f)->{
|
||||
loadControl(f.getAbsolutePath(),layout);
|
||||
});
|
||||
sel.show();*/
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(layout.getContext());
|
||||
builder.setTitle(R.string.global_load);
|
||||
builder.setPositiveButton(android.R.string.cancel, null);
|
||||
|
||||
final AlertDialog dialog = builder.create();
|
||||
FileListView flv = new FileListView(dialog, "json");
|
||||
if(Build.VERSION.SDK_INT < 29)flv.listFileAt(Tools.CTRLMAP_PATH);
|
||||
else flv.lockPathAt(Tools.CTRLMAP_PATH);
|
||||
flv.setFileSelectedListener(new FileSelectedListener(){
|
||||
|
||||
@Override
|
||||
public void onFileSelected(File file, String path) {
|
||||
loadControl(path,layout);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
dialog.setView(flv);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private static void loadControl(String path,ControlLayout layout) {
|
||||
try {
|
||||
layout.loadLayout(path);
|
||||
selectedName = new File(path).getName();
|
||||
// Remove `.json`
|
||||
selectedName = selectedName.substring(0, selectedName.length() - 5);
|
||||
} catch (Exception e) {
|
||||
Tools.showError(layout.getContext(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.content.*;
|
||||
import android.net.*;
|
||||
import androidx.browser.customtabs.*;
|
||||
|
||||
public class CustomTabs {
|
||||
|
||||
public static void openTab(Context context, String url) {
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setShowTitle(true);
|
||||
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
customTabsIntent.launchUrl(context, Uri.parse(url));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
import java.util.*;
|
||||
|
||||
public class DisplayableLocale {
|
||||
public final Locale mLocale;
|
||||
public final CharSequence mName;
|
||||
|
||||
private static Locale processStringLocale(String locale) {
|
||||
if (locale.contains("-")) {
|
||||
String[] split = locale.split("-");
|
||||
return new Locale(split[0], split[1]);
|
||||
} else {
|
||||
return new Locale(locale);
|
||||
}
|
||||
}
|
||||
|
||||
public DisplayableLocale(String locale) {
|
||||
this(processStringLocale(locale));
|
||||
}
|
||||
|
||||
public DisplayableLocale(Locale locale) {
|
||||
this(locale, locale.getDisplayName(locale));
|
||||
}
|
||||
|
||||
public DisplayableLocale(Locale locale, CharSequence name) {
|
||||
mLocale = locale;
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public Locale toLocale() {
|
||||
return mLocale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mName.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EfficientAndroidLWJGLKeycode {
|
||||
|
||||
//This old version of this class was using an ArrayMap, a generic Key -> Value data structure.
|
||||
//The key being the android keycode from a KeyEvent
|
||||
//The value its LWJGL equivalent.
|
||||
private static final int KEYCODE_COUNT = 103;
|
||||
private static final int[] androidKeycodes = new int[KEYCODE_COUNT];
|
||||
private static final short[] LWJGLKeycodes = new short[KEYCODE_COUNT];
|
||||
private static String[] androidKeyNameArray; /* = new String[androidKeycodes.length]; */
|
||||
|
||||
static {
|
||||
|
||||
/* BINARY SEARCH IS PERFORMED ON THE androidKeycodes ARRAY !
|
||||
WHEN ADDING A MAPPING, ADD IT SO THE androidKeycodes ARRAY STAYS SORTED ! */
|
||||
// Mapping Android Keycodes to LWJGL Keycodes
|
||||
add(KeyEvent.KEYCODE_UNKNOWN,LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN);
|
||||
add(KeyEvent.KEYCODE_HOME, LWJGLGLFWKeycode.GLFW_KEY_HOME);
|
||||
// Escape key
|
||||
add(KeyEvent.KEYCODE_BACK, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
|
||||
|
||||
// 0-9 keys
|
||||
add(KeyEvent.KEYCODE_0, LWJGLGLFWKeycode.GLFW_KEY_0); //7
|
||||
add(KeyEvent.KEYCODE_1, LWJGLGLFWKeycode.GLFW_KEY_1);
|
||||
add(KeyEvent.KEYCODE_2, LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
add(KeyEvent.KEYCODE_3, LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
add(KeyEvent.KEYCODE_4, LWJGLGLFWKeycode.GLFW_KEY_4);
|
||||
add(KeyEvent.KEYCODE_5, LWJGLGLFWKeycode.GLFW_KEY_5);
|
||||
add(KeyEvent.KEYCODE_6, LWJGLGLFWKeycode.GLFW_KEY_6);
|
||||
add(KeyEvent.KEYCODE_7, LWJGLGLFWKeycode.GLFW_KEY_7);
|
||||
add(KeyEvent.KEYCODE_8, LWJGLGLFWKeycode.GLFW_KEY_8);
|
||||
add(KeyEvent.KEYCODE_9, LWJGLGLFWKeycode.GLFW_KEY_9); //16
|
||||
|
||||
add(KeyEvent.KEYCODE_POUND,LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
|
||||
// Arrow keys
|
||||
add(KeyEvent.KEYCODE_DPAD_UP, LWJGLGLFWKeycode.GLFW_KEY_UP); //19
|
||||
add(KeyEvent.KEYCODE_DPAD_DOWN, LWJGLGLFWKeycode.GLFW_KEY_DOWN);
|
||||
add(KeyEvent.KEYCODE_DPAD_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT);
|
||||
add(KeyEvent.KEYCODE_DPAD_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT); //22
|
||||
|
||||
// A-Z keys
|
||||
add(KeyEvent.KEYCODE_A, LWJGLGLFWKeycode.GLFW_KEY_A); //29
|
||||
add(KeyEvent.KEYCODE_B, LWJGLGLFWKeycode.GLFW_KEY_B);
|
||||
add(KeyEvent.KEYCODE_C, LWJGLGLFWKeycode.GLFW_KEY_C);
|
||||
add(KeyEvent.KEYCODE_D, LWJGLGLFWKeycode.GLFW_KEY_D);
|
||||
add(KeyEvent.KEYCODE_E, LWJGLGLFWKeycode.GLFW_KEY_E);
|
||||
add(KeyEvent.KEYCODE_F, LWJGLGLFWKeycode.GLFW_KEY_F);
|
||||
add(KeyEvent.KEYCODE_G, LWJGLGLFWKeycode.GLFW_KEY_G);
|
||||
add(KeyEvent.KEYCODE_H, LWJGLGLFWKeycode.GLFW_KEY_H);
|
||||
add(KeyEvent.KEYCODE_I, LWJGLGLFWKeycode.GLFW_KEY_I);
|
||||
add(KeyEvent.KEYCODE_J, LWJGLGLFWKeycode.GLFW_KEY_J);
|
||||
add(KeyEvent.KEYCODE_K, LWJGLGLFWKeycode.GLFW_KEY_K);
|
||||
add(KeyEvent.KEYCODE_L, LWJGLGLFWKeycode.GLFW_KEY_L);
|
||||
add(KeyEvent.KEYCODE_M, LWJGLGLFWKeycode.GLFW_KEY_M);
|
||||
add(KeyEvent.KEYCODE_N, LWJGLGLFWKeycode.GLFW_KEY_N);
|
||||
add(KeyEvent.KEYCODE_O, LWJGLGLFWKeycode.GLFW_KEY_O);
|
||||
add(KeyEvent.KEYCODE_P, LWJGLGLFWKeycode.GLFW_KEY_P);
|
||||
add(KeyEvent.KEYCODE_Q, LWJGLGLFWKeycode.GLFW_KEY_Q);
|
||||
add(KeyEvent.KEYCODE_R, LWJGLGLFWKeycode.GLFW_KEY_R);
|
||||
add(KeyEvent.KEYCODE_S, LWJGLGLFWKeycode.GLFW_KEY_S);
|
||||
add(KeyEvent.KEYCODE_T, LWJGLGLFWKeycode.GLFW_KEY_T);
|
||||
add(KeyEvent.KEYCODE_U, LWJGLGLFWKeycode.GLFW_KEY_U);
|
||||
add(KeyEvent.KEYCODE_V, LWJGLGLFWKeycode.GLFW_KEY_V);
|
||||
add(KeyEvent.KEYCODE_W, LWJGLGLFWKeycode.GLFW_KEY_W);
|
||||
add(KeyEvent.KEYCODE_X, LWJGLGLFWKeycode.GLFW_KEY_X);
|
||||
add(KeyEvent.KEYCODE_Y, LWJGLGLFWKeycode.GLFW_KEY_Y);
|
||||
add(KeyEvent.KEYCODE_Z, LWJGLGLFWKeycode.GLFW_KEY_Z); //54
|
||||
|
||||
|
||||
add(KeyEvent.KEYCODE_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
|
||||
add(KeyEvent.KEYCODE_PERIOD, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
|
||||
|
||||
// Alt keys
|
||||
add(KeyEvent.KEYCODE_ALT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT);
|
||||
add(KeyEvent.KEYCODE_ALT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_ALT);
|
||||
|
||||
// Shift keys
|
||||
add(KeyEvent.KEYCODE_SHIFT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT);
|
||||
add(KeyEvent.KEYCODE_SHIFT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_SHIFT);
|
||||
|
||||
add(KeyEvent.KEYCODE_TAB, LWJGLGLFWKeycode.GLFW_KEY_TAB);
|
||||
add(KeyEvent.KEYCODE_SPACE, LWJGLGLFWKeycode.GLFW_KEY_SPACE);
|
||||
add(KeyEvent.KEYCODE_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER); //66
|
||||
add(KeyEvent.KEYCODE_DEL, LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE); // Backspace
|
||||
add(KeyEvent.KEYCODE_GRAVE, LWJGLGLFWKeycode.GLFW_KEY_GRAVE_ACCENT);
|
||||
add(KeyEvent.KEYCODE_MINUS, LWJGLGLFWKeycode.GLFW_KEY_MINUS);
|
||||
add(KeyEvent.KEYCODE_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL);
|
||||
add(KeyEvent.KEYCODE_LEFT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_LEFT_BRACKET);
|
||||
add(KeyEvent.KEYCODE_RIGHT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_BRACKET);
|
||||
add(KeyEvent.KEYCODE_BACKSLASH, LWJGLGLFWKeycode.GLFW_KEY_BACKSLASH);
|
||||
add(KeyEvent.KEYCODE_SEMICOLON, LWJGLGLFWKeycode.GLFW_KEY_SEMICOLON); //74
|
||||
|
||||
add(KeyEvent.KEYCODE_SLASH, LWJGLGLFWKeycode.GLFW_KEY_SLASH); //76
|
||||
add(KeyEvent.KEYCODE_AT,LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
|
||||
add(KeyEvent.KEYCODE_PLUS, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
|
||||
|
||||
// Page keys
|
||||
add(KeyEvent.KEYCODE_PAGE_UP, LWJGLGLFWKeycode.GLFW_KEY_PAGE_UP); //92
|
||||
add(KeyEvent.KEYCODE_PAGE_DOWN, LWJGLGLFWKeycode.GLFW_KEY_PAGE_DOWN);
|
||||
|
||||
add(KeyEvent.KEYCODE_ESCAPE, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
|
||||
|
||||
// Control keys
|
||||
add(KeyEvent.KEYCODE_CTRL_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL);
|
||||
add(KeyEvent.KEYCODE_CTRL_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_CONTROL);
|
||||
|
||||
add(KeyEvent.KEYCODE_CAPS_LOCK, LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK);
|
||||
add(KeyEvent.KEYCODE_BREAK, LWJGLGLFWKeycode.GLFW_KEY_PAUSE);
|
||||
add(KeyEvent.KEYCODE_INSERT, LWJGLGLFWKeycode.GLFW_KEY_INSERT);
|
||||
|
||||
// Fn keys
|
||||
add(KeyEvent.KEYCODE_F1, LWJGLGLFWKeycode.GLFW_KEY_F1); //131
|
||||
add(KeyEvent.KEYCODE_F2, LWJGLGLFWKeycode.GLFW_KEY_F2);
|
||||
add(KeyEvent.KEYCODE_F3, LWJGLGLFWKeycode.GLFW_KEY_F3);
|
||||
add(KeyEvent.KEYCODE_F4, LWJGLGLFWKeycode.GLFW_KEY_F4);
|
||||
add(KeyEvent.KEYCODE_F5, LWJGLGLFWKeycode.GLFW_KEY_F5);
|
||||
add(KeyEvent.KEYCODE_F6, LWJGLGLFWKeycode.GLFW_KEY_F6);
|
||||
add(KeyEvent.KEYCODE_F7, LWJGLGLFWKeycode.GLFW_KEY_F7);
|
||||
add(KeyEvent.KEYCODE_F8, LWJGLGLFWKeycode.GLFW_KEY_F8);
|
||||
add(KeyEvent.KEYCODE_F9, LWJGLGLFWKeycode.GLFW_KEY_F9);
|
||||
add(KeyEvent.KEYCODE_F10, LWJGLGLFWKeycode.GLFW_KEY_F10);
|
||||
add(KeyEvent.KEYCODE_F11, LWJGLGLFWKeycode.GLFW_KEY_F11);
|
||||
add(KeyEvent.KEYCODE_F12, LWJGLGLFWKeycode.GLFW_KEY_F12); //142
|
||||
|
||||
// Num keys
|
||||
add(KeyEvent.KEYCODE_NUM_LOCK, LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK); //143
|
||||
add(KeyEvent.KEYCODE_NUMPAD_0, LWJGLGLFWKeycode.GLFW_KEY_0);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_1, LWJGLGLFWKeycode.GLFW_KEY_1);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_2, LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_3, LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_4, LWJGLGLFWKeycode.GLFW_KEY_4);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_5, LWJGLGLFWKeycode.GLFW_KEY_5);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_6, LWJGLGLFWKeycode.GLFW_KEY_6);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_7, LWJGLGLFWKeycode.GLFW_KEY_7);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_8, LWJGLGLFWKeycode.GLFW_KEY_8);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_9, LWJGLGLFWKeycode.GLFW_KEY_9);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_DIVIDE, LWJGLGLFWKeycode.GLFW_KEY_KP_DIVIDE);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, LWJGLGLFWKeycode.GLFW_KEY_KP_MULTIPLY);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, LWJGLGLFWKeycode.GLFW_KEY_KP_SUBTRACT);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_ADD, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_DOT, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER);
|
||||
add(KeyEvent.KEYCODE_NUMPAD_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL); //161
|
||||
|
||||
}
|
||||
|
||||
private static short index = 0;
|
||||
|
||||
private static void add(int androidKeycode, short LWJGLKeycode){
|
||||
androidKeycodes[index] = androidKeycode;
|
||||
LWJGLKeycodes[index] = LWJGLKeycode;
|
||||
++index;
|
||||
}
|
||||
|
||||
|
||||
public static boolean containsKey(int keycode){
|
||||
return getIndexByKey(keycode) >= 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String[] generateKeyName() {
|
||||
if (androidKeyNameArray == null) {
|
||||
androidKeyNameArray = new String[androidKeycodes.length];
|
||||
for(int i=0; i < androidKeyNameArray.length; ++i){
|
||||
androidKeyNameArray[i] = KeyEvent.keyCodeToString(androidKeycodes[i]).replace("KEYCODE_", "");
|
||||
}
|
||||
}
|
||||
return androidKeyNameArray;
|
||||
}
|
||||
|
||||
public static void execKey(KeyEvent keyEvent) {
|
||||
execKey(keyEvent, getIndexByKey(keyEvent.getKeyCode()));
|
||||
}
|
||||
|
||||
|
||||
public static void execKey(KeyEvent keyEvent, int valueIndex) {
|
||||
//valueIndex points to where the value is stored in the array.
|
||||
CallbackBridge.holdingAlt = keyEvent.isAltPressed();
|
||||
CallbackBridge.holdingCapslock = keyEvent.isCapsLockOn();
|
||||
CallbackBridge.holdingCtrl = keyEvent.isCtrlPressed();
|
||||
CallbackBridge.holdingNumlock = keyEvent.isNumLockOn();
|
||||
CallbackBridge.holdingShift = keyEvent.isShiftPressed();
|
||||
|
||||
try {
|
||||
System.out.println(keyEvent.getKeyCode() + " " +keyEvent.getDisplayLabel());
|
||||
char key = (char)(keyEvent.getUnicodeChar() != 0 ? keyEvent.getUnicodeChar() : '\u0000');
|
||||
sendKeyPress(
|
||||
getValueByIndex(valueIndex),
|
||||
key,
|
||||
0,
|
||||
CallbackBridge.getCurrentMods(),
|
||||
keyEvent.getAction() == KeyEvent.ACTION_DOWN);
|
||||
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void execKeyIndex(int index){
|
||||
//Send a quick key press.
|
||||
sendKeyPress(getValueByIndex(index));
|
||||
}
|
||||
|
||||
public static int getValueByIndex(int index) {
|
||||
return LWJGLKeycodes[index];
|
||||
}
|
||||
|
||||
public static int getIndexByKey(int key){
|
||||
return Arrays.binarySearch(androidKeycodes, key);
|
||||
}
|
||||
|
||||
public static short getValue(int key){
|
||||
return LWJGLKeycodes[Arrays.binarySearch(androidKeycodes, key)];
|
||||
}
|
||||
|
||||
public static int getIndexByValue(int lwjglKey) {
|
||||
//Since the LWJGL keycodes aren't sorted, linear search is used.
|
||||
//You should avoid using this function on performance critical areas
|
||||
for (int i = 0; i < LWJGLKeycodes.length; i++) {
|
||||
if(LWJGLKeycodes[i] == lwjglKey) return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
@Keep
|
||||
public class ExitActivity extends AppCompatActivity {
|
||||
public static void showExitMessage(Context ctx, int code) {
|
||||
Intent i = new Intent(ctx,ExitActivity.class);
|
||||
i.putExtra("code",code);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
ctx.startActivity(i);
|
||||
}
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
int code = -1;
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if(extras != null) {
|
||||
code = extras.getInt("code",-1);
|
||||
}
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(getString(R.string.mcn_exit_title,code))
|
||||
.setPositiveButton(android.R.string.ok,(dialog,which)->{
|
||||
dialog.dismiss();
|
||||
ExitActivity.this.finish();
|
||||
}).setOnCancelListener((z)->{
|
||||
ExitActivity.this.finish();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.kdt.pojavlaunch.utils.FileUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* An activity dedicated to importing control files.
|
||||
*/
|
||||
public class ImportControlActivity extends Activity {
|
||||
|
||||
private Uri mUriData;
|
||||
private boolean mHasIntentChanged = true;
|
||||
private volatile boolean mIsFileVerified = false;
|
||||
|
||||
private EditText mEditText;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Tools.initContextConstants(getApplicationContext());
|
||||
|
||||
setContentView(R.layout.import_control_layout);
|
||||
mEditText = findViewById(R.id.editText_import_control_file_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the previous loaded intent
|
||||
* @param intent the intent used to replace the old one.
|
||||
*/
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if(intent != null) setIntent(intent);
|
||||
mHasIntentChanged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all over again if the intent changed.
|
||||
*/
|
||||
@Override
|
||||
protected void onPostResume() {
|
||||
super.onPostResume();
|
||||
if(!mHasIntentChanged) return;
|
||||
mIsFileVerified = false;
|
||||
getUriData();
|
||||
mEditText.setText(getNameFromURI(mUriData));
|
||||
mHasIntentChanged = false;
|
||||
|
||||
//Import and verify thread
|
||||
//Kill the app if the file isn't valid.
|
||||
new Thread(() -> {
|
||||
importControlFile("TMP_IMPORT_FILE");
|
||||
|
||||
if(verify())mIsFileVerified = true;
|
||||
else runOnUiThread(() -> {
|
||||
Toast.makeText(
|
||||
ImportControlActivity.this,
|
||||
getText(R.string.import_control_invalid_file),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finishAndRemoveTask();
|
||||
});
|
||||
}).start();
|
||||
|
||||
//Auto show the keyboard
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
InputMethodManager imm = (InputMethodManager) getApplicationContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
|
||||
mEditText.setSelection(mEditText.getText().length());
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the import.
|
||||
* @param view the view which called the function
|
||||
*/
|
||||
public void startImport(View view) {
|
||||
String fileName = trimFileName(mEditText.getText().toString());
|
||||
//Step 1 check for suffixes.
|
||||
if(!isFileNameValid(fileName)){
|
||||
Toast.makeText(this, getText(R.string.import_control_invalid_name), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if(!mIsFileVerified){
|
||||
Toast.makeText(this, getText(R.string.import_control_verifying_file), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
new File(Tools.CTRLMAP_PATH + "/TMP_IMPORT_FILE.json").renameTo(new File(Tools.CTRLMAP_PATH + "/" + fileName + ".json"));
|
||||
Toast.makeText(getApplicationContext(), getText(R.string.import_control_done), Toast.LENGTH_SHORT).show();
|
||||
finishAndRemoveTask();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a the file from the Intent data with a provided name into the controlmap folder.
|
||||
* @param fileName The file name to use.
|
||||
* @return whether the file was successfully imported
|
||||
*/
|
||||
private boolean importControlFile(String fileName){
|
||||
InputStream is;
|
||||
try {
|
||||
is = getContentResolver().openInputStream(mUriData);
|
||||
|
||||
OutputStream os = new FileOutputStream(Tools.CTRLMAP_PATH + "/" + fileName + ".json");
|
||||
byte[] buffer = new byte[1024];
|
||||
while(is.read(buffer) != -1)
|
||||
os.write(buffer);
|
||||
|
||||
os.close();
|
||||
is.close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if the clean version of the filename is valid.
|
||||
* @param fileName the string to test
|
||||
* @return whether the filename is valid
|
||||
*/
|
||||
private static boolean isFileNameValid(String fileName){
|
||||
fileName = trimFileName(fileName);
|
||||
|
||||
if(fileName.isEmpty()) return false;
|
||||
if (FileUtils.exists(Tools.CTRLMAP_PATH + "/" + fileName + ".json")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove or undesirable chars from the string
|
||||
* @param fileName The string to trim
|
||||
* @return The trimmed string
|
||||
*/
|
||||
private static String trimFileName(String fileName){
|
||||
return fileName
|
||||
.replace(".json", "")
|
||||
.replaceAll("%..", "/")
|
||||
.replace("/", "")
|
||||
.replace("\\", "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get an Uri from the various sources
|
||||
*/
|
||||
private void getUriData(){
|
||||
mUriData = getIntent().getData();
|
||||
if(mUriData != null) return;
|
||||
try {
|
||||
mUriData = getIntent().getClipData().getItemAt(0).getUri();
|
||||
}catch (Exception ignored){}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the control file is valid
|
||||
* @return Whether the control file is valid
|
||||
*/
|
||||
private static boolean verify(){
|
||||
try{
|
||||
String jsonLayoutData = Tools.read(Tools.CTRLMAP_PATH + "/TMP_IMPORT_FILE.json");
|
||||
JSONObject layoutJobj = new JSONObject(jsonLayoutData);
|
||||
return layoutJobj.has("version") && layoutJobj.has("mControlDataList");
|
||||
|
||||
}catch (JSONException | IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String getNameFromURI(Uri uri) {
|
||||
Cursor c = getContentResolver().query(uri, null, null, null, null);
|
||||
c.moveToFirst();
|
||||
String fileName = c.getString(c.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
c.close();
|
||||
return trimFileName(fileName);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
import android.os.*;
|
||||
|
||||
public class InstallerTask extends AsyncTask<String, Void, String>
|
||||
{
|
||||
@Override
|
||||
protected String doInBackground(String[] p1)
|
||||
{
|
||||
try
|
||||
{
|
||||
downloadLibraries(p1[0]);
|
||||
dexMinecraftLibs();
|
||||
downloadMinecraft(p1[0]);
|
||||
dexMinecraftClient(p1[0]);
|
||||
downloadAssets(p1[0]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return e.getMessage();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(String result)
|
||||
{
|
||||
super.onPostExecute(result);
|
||||
|
||||
if(result == null){
|
||||
//No errors
|
||||
}
|
||||
}
|
||||
private void downloadLibraries(String versionName) throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
private void dexMinecraftLibs() throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
private void downloadMinecraft(String versionName) throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
private void dexMinecraftClient(String version) throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
private void downloadAssets(String versionName) throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
public class JAssetInfo
|
||||
{
|
||||
public String hash;
|
||||
public int size;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class JAssets {
|
||||
public boolean map_to_resources;
|
||||
public Map<String, JAssetInfo> objects;
|
||||
}
|
||||
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import java.util.Map;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
@Keep
|
||||
public class JMinecraftVersionList {
|
||||
public static final String TYPE_OLD_ALPHA = "old_alpha";
|
||||
public static final String TYPE_OLD_BETA = "old_beta";
|
||||
public static final String TYPE_RELEASE = "release";
|
||||
public static final String TYPE_SNAPSHOT = "snapshot";
|
||||
public Map<String, String> latest;
|
||||
public Version[] versions;
|
||||
|
||||
@Keep
|
||||
public static class Version {
|
||||
// Since 1.13, so it's one of ways to check
|
||||
public Arguments arguments;
|
||||
|
||||
public AssetIndex assetIndex;
|
||||
|
||||
public String assets;
|
||||
public Map<String, MinecraftClientInfo> downloads;
|
||||
public String id;
|
||||
public String inheritsFrom;
|
||||
public JavaVersionInfo javaVersion;
|
||||
public DependentLibrary[] libraries;
|
||||
public String mainClass;
|
||||
public String minecraftArguments;
|
||||
public int minimumLauncherVersion;
|
||||
public DependentLibrary optifineLib;
|
||||
public String releaseTime;
|
||||
public String time;
|
||||
public String type;
|
||||
public String url;
|
||||
public String sha1;
|
||||
}
|
||||
@Keep
|
||||
public static class JavaVersionInfo {
|
||||
public String component;
|
||||
public int majorVersion;
|
||||
}
|
||||
|
||||
// Since 1.13
|
||||
@Keep
|
||||
public static class Arguments {
|
||||
public Object[] game;
|
||||
public Object[] jvm;
|
||||
|
||||
@Keep
|
||||
public static class ArgValue {
|
||||
public ArgRules[] rules;
|
||||
public String value;
|
||||
|
||||
// TLauncher styled argument...
|
||||
public String[] values;
|
||||
|
||||
@Keep
|
||||
public static class ArgRules {
|
||||
public String action;
|
||||
public String features;
|
||||
}
|
||||
}
|
||||
}
|
||||
@Keep
|
||||
public static class AssetIndex {
|
||||
public String id, sha1, url;
|
||||
public long size, totalSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import java.io.*;;
|
||||
|
||||
/**
|
||||
* This account data format is deprecated.
|
||||
* The current account data format is JSON on net.kdt.pojavlaunch.value.MinecraftAccount.
|
||||
* This class remain for account data migrator only.
|
||||
* Methods for saving/exporting on this format are no longer available.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MCProfile
|
||||
{
|
||||
private static String[] emptyBuilder = new String[]{
|
||||
"1.9", //Version
|
||||
"ProfileIDEmpty",
|
||||
"AccessToken",
|
||||
"AccessTokenEmpty",
|
||||
"Steve"
|
||||
};
|
||||
|
||||
public static MCProfile.Builder load(String pofFilePath) {
|
||||
try {
|
||||
String pofContent = Tools.read(pofFilePath);
|
||||
return parse(pofContent);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to load Profile " + pofFilePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static MCProfile.Builder parse(String content) {
|
||||
if (content == null || content.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MCProfile.Builder builder = new MCProfile.Builder();
|
||||
|
||||
String[] profileInfos = content.split(":");
|
||||
|
||||
String cltk = profileInfos[0];
|
||||
String prtk = profileInfos[1];
|
||||
String acct = profileInfos[2];
|
||||
String name = profileInfos[3];
|
||||
String vers = profileInfos[4];
|
||||
String isAc = profileInfos[5];
|
||||
|
||||
//System.out.println("parse THE VER = " + vers);
|
||||
|
||||
builder.setClientID(cltk);
|
||||
builder.setProfileID(prtk);
|
||||
builder.setAccessToken(acct);
|
||||
builder.setUsername(name);
|
||||
builder.setVersion(vers);
|
||||
builder.setIsMojangAccount(Boolean.parseBoolean(isAc));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static MCProfile.Builder loadSafety(String pofFilePath) {
|
||||
try {
|
||||
return load(pofFilePath);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// return new MCProfile.Builder();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder implements Serializable
|
||||
{
|
||||
private String[] fullArgs = new String[6];
|
||||
private boolean isMojangAccount = true;
|
||||
|
||||
public Builder()
|
||||
{
|
||||
fullArgs = emptyBuilder;
|
||||
setClientID("0");
|
||||
setProfileID("00000000-0000-0000-0000-000000000000");
|
||||
setAccessToken("0");
|
||||
}
|
||||
|
||||
public boolean isMojangAccount()
|
||||
{
|
||||
return isMojangAccount;
|
||||
}
|
||||
|
||||
public String getVersion()
|
||||
{
|
||||
return fullArgs[0];
|
||||
}
|
||||
|
||||
public String getClientID()
|
||||
{
|
||||
return fullArgs[1];
|
||||
}
|
||||
|
||||
public String getProfileID()
|
||||
{
|
||||
return fullArgs[2];
|
||||
}
|
||||
|
||||
public String getAccessToken()
|
||||
{
|
||||
return fullArgs[3];
|
||||
}
|
||||
|
||||
public String getUsername()
|
||||
{
|
||||
return fullArgs[4];
|
||||
}
|
||||
|
||||
public void setIsMojangAccount(boolean value)
|
||||
{
|
||||
isMojangAccount = value;
|
||||
}
|
||||
|
||||
public void setVersion(String value)
|
||||
{
|
||||
fullArgs[0] = value;
|
||||
}
|
||||
|
||||
public void setClientID(String value)
|
||||
{
|
||||
fullArgs[1] = value;
|
||||
}
|
||||
|
||||
public void setProfileID(String value)
|
||||
{
|
||||
fullArgs[2] = value;
|
||||
}
|
||||
|
||||
public void setAccessToken(String value)
|
||||
{
|
||||
fullArgs[3] = value;
|
||||
}
|
||||
|
||||
public void setUsername(String value)
|
||||
{
|
||||
fullArgs[4] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.*;
|
||||
|
||||
|
||||
import net.kdt.pojavlaunch.customcontrols.*;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.MCOptionUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_SUSTAINED_PERFORMANCE;
|
||||
|
||||
public class MainActivity extends BaseMainActivity {
|
||||
public static ControlLayout mControlLayout;
|
||||
|
||||
private MCOptionUtils.MCOptionListener optionListener;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initLayout(R.layout.main_with_customctrl);
|
||||
|
||||
// Set the sustained performance mode for available APIs
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
getWindow().setSustainedPerformanceMode(PREF_SUSTAINED_PERFORMANCE);
|
||||
|
||||
super.ingameControlsEditorListener = menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_ctrl_load:
|
||||
CustomControlsActivity.load(mControlLayout);
|
||||
break;
|
||||
case R.id.menu_ctrl_add:
|
||||
mControlLayout.addControlButton(new ControlData("New"));
|
||||
break;
|
||||
case R.id.menu_ctrl_add_drawer:
|
||||
mControlLayout.addDrawer(new ControlDrawerData());
|
||||
break;
|
||||
case R.id.menu_ctrl_selectdefault:
|
||||
CustomControlsActivity.dialogSelectDefaultCtrl(mControlLayout);
|
||||
break;
|
||||
case R.id.menu_ctrl_save:
|
||||
CustomControlsActivity.save(true,mControlLayout);
|
||||
break;
|
||||
}
|
||||
//Toast.makeText(MainActivity.this, menuItem.getTitle() + ":" + menuItem.getItemId(), Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
};
|
||||
|
||||
// Recompute the gui scale when options are changed
|
||||
optionListener = MCOptionUtils::getMcScale;
|
||||
MCOptionUtils.addMCOptionListener(optionListener);
|
||||
|
||||
mControlLayout = findViewById(R.id.main_control_layout);
|
||||
mControlLayout.setModifiable(false);
|
||||
try {
|
||||
mControlLayout.loadLayout(LauncherPreferences.PREF_DEFAULTCTRL_PATH);
|
||||
} catch(IOException e) {
|
||||
try {
|
||||
mControlLayout.loadLayout(Tools.CTRLDEF_FILE);
|
||||
DEFAULT_PREF.edit().putString("defaultCtrl",Tools.CTRLDEF_FILE).commit();
|
||||
} catch (IOException ioException) {
|
||||
Tools.showError(this, ioException);
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Tools.showError(this, th);
|
||||
}
|
||||
|
||||
// toggleGui(null);
|
||||
mControlLayout.toggleControlVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
|
||||
// Reload PREF_DEFAULTCTRL_PATH
|
||||
LauncherPreferences.loadPreferences(getApplicationContext());
|
||||
try {
|
||||
mControlLayout.loadLayout(LauncherPreferences.PREF_DEFAULTCTRL_PATH);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
//if(isInEditor) CustomControlsActivity.save(true,mControlLayout);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,600 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import static net.kdt.pojavlaunch.BaseMainActivity.touchCharInput;
|
||||
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
|
||||
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
|
||||
import static org.lwjgl.glfw.CallbackBridge.windowHeight;
|
||||
import static org.lwjgl.glfw.CallbackBridge.windowWidth;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.*;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.google.android.material.math.MathUtils;
|
||||
|
||||
import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.utils.JREUtils;
|
||||
import net.kdt.pojavlaunch.utils.MCOptionUtils;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
/**
|
||||
* Class dealing with showing minecraft surface and taking inputs to dispatch them to minecraft
|
||||
*/
|
||||
public class MinecraftGLView extends TextureView {
|
||||
/* Gamepad object for gamepad inputs, instantiated on need */
|
||||
private Gamepad gamepad = null;
|
||||
/* Pointer Debug textview, used to show info about the pointer state */
|
||||
private TextView pointerDebugText;
|
||||
|
||||
/* Resolution scaler option, allow downsizing a window */
|
||||
private final float scaleFactor = LauncherPreferences.DEFAULT_PREF.getInt("resolutionRatio",100)/100f;
|
||||
/* Display properties, such as resolution and DPI */
|
||||
private final DisplayMetrics displayMetrics = Tools.getDisplayMetrics((Activity) getContext());
|
||||
/* Sensitivity, adjusted according to screen size */
|
||||
private final double sensitivityFactor = (1.4 * (1080f/ displayMetrics.heightPixels));
|
||||
/* Use to detect simple and double taps */
|
||||
private final TapDetector singleTapDetector = new TapDetector(1, TapDetector.DETECTION_METHOD_BOTH);
|
||||
private final TapDetector doubleTapDetector = new TapDetector(2, TapDetector.DETECTION_METHOD_DOWN);
|
||||
/* MC GUI scale, listened by MCOptionUtils */
|
||||
private int GUIScale = getMcScale();
|
||||
private MCOptionUtils.MCOptionListener GUIScaleListener = () -> GUIScale = getMcScale();
|
||||
/* Surface ready listener, used by the activity to launch minecraft */
|
||||
SurfaceReadyListener surfaceReadyListener = null;
|
||||
|
||||
/* List of hotbarKeys, used when clicking on the hotbar */
|
||||
private static final int[] hotbarKeys = {
|
||||
LWJGLGLFWKeycode.GLFW_KEY_1, LWJGLGLFWKeycode.GLFW_KEY_2, LWJGLGLFWKeycode.GLFW_KEY_3,
|
||||
LWJGLGLFWKeycode.GLFW_KEY_4, LWJGLGLFWKeycode.GLFW_KEY_5, LWJGLGLFWKeycode.GLFW_KEY_6,
|
||||
LWJGLGLFWKeycode.GLFW_KEY_7, LWJGLGLFWKeycode.GLFW_KEY_8, LWJGLGLFWKeycode.GLFW_KEY_9};
|
||||
/* Last hotbar button (0-9) registered */
|
||||
private int lastHotbarKey = -1;
|
||||
/* Events can start with only a move instead of an pointerDown due to mouse passthrough */
|
||||
private boolean shouldBeDown = false;
|
||||
/* When fingers are really near to each other, it tends to either swap or remove a pointer ! */
|
||||
private int lastPointerCount = 0;
|
||||
/* Mouse positions, scaled by the scaleFactor */
|
||||
private float mouse_x, mouse_y;
|
||||
/* Previous MotionEvent position, not scale */
|
||||
private float prevX, prevY;
|
||||
/* PointerID used for the moving camera */
|
||||
private int currentPointerID = -1000;
|
||||
/* Initial first pointer positions non-scaled, used to test touch sloppiness */
|
||||
private float initialX, initialY;
|
||||
/* Last first pointer positions non-scaled, used to scroll distance */
|
||||
private float scrollLastInitialX, scrollLastInitialY;
|
||||
/* How much distance a finger has to go for touch sloppiness to be disabled */
|
||||
public static final int FINGER_STILL_THRESHOLD = (int) Tools.dpToPx(9);
|
||||
/* How much distance a finger has to go to scroll */
|
||||
public static final int FINGER_SCROLL_THRESHOLD = (int) Tools.dpToPx(6);
|
||||
/* Handle hotbar throw button and mouse mining button */
|
||||
public static final int MSG_LEFT_MOUSE_BUTTON_CHECK = 1028;
|
||||
public static final int MSG_DROP_ITEM_BUTTON_CHECK = 1029;
|
||||
private final Handler theHandler = new Handler(Looper.getMainLooper()) {
|
||||
public void handleMessage(Message msg) {
|
||||
if(msg.what == MSG_LEFT_MOUSE_BUTTON_CHECK) {
|
||||
if (LauncherPreferences.PREF_DISABLE_GESTURES) return;
|
||||
float x = CallbackBridge.mouseX;
|
||||
float y = CallbackBridge.mouseY;
|
||||
if (CallbackBridge.isGrabbing() &&
|
||||
MathUtils.dist(x, y, initialX, initialY) < FINGER_STILL_THRESHOLD) {
|
||||
triggeredLeftMouseButton = true;
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(msg.what == MSG_DROP_ITEM_BUTTON_CHECK) {
|
||||
if(CallbackBridge.isGrabbing()){
|
||||
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_Q);
|
||||
theHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 600);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
/* Whether the button was triggered, used by the handler */
|
||||
private static boolean triggeredLeftMouseButton = false;
|
||||
|
||||
|
||||
public MinecraftGLView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public MinecraftGLView(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
//Fixes freeform and dex mode having transparent glass,
|
||||
//since it forces android to used the background color of the view/layout behind it.
|
||||
setOpaque(false);
|
||||
setFocusable(true);
|
||||
|
||||
MCOptionUtils.addMCOptionListener(GUIScaleListener);
|
||||
}
|
||||
|
||||
/** Initialize the view and all its settings */
|
||||
public void start(){
|
||||
// Add the pointer debug textview
|
||||
pointerDebugText = new TextView(getContext());
|
||||
pointerDebugText.setX(0);
|
||||
pointerDebugText.setY(0);
|
||||
pointerDebugText.setVisibility(GONE);
|
||||
((ViewGroup)getParent()).addView(pointerDebugText);
|
||||
|
||||
setSurfaceTextureListener(new SurfaceTextureListener() {
|
||||
private boolean isCalled = false;
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
|
||||
windowWidth = Tools.getDisplayFriendlyRes(width, scaleFactor);
|
||||
windowHeight = Tools.getDisplayFriendlyRes(height, scaleFactor);
|
||||
texture.setDefaultBufferSize(windowWidth, windowHeight);
|
||||
|
||||
//Load Minecraft options:
|
||||
MCOptionUtils.load();
|
||||
MCOptionUtils.set("overrideWidth", String.valueOf(windowWidth));
|
||||
MCOptionUtils.set("overrideHeight", String.valueOf(windowHeight));
|
||||
MCOptionUtils.save();
|
||||
getMcScale();
|
||||
// Should we do that?
|
||||
if(isCalled) return;
|
||||
isCalled = true;
|
||||
|
||||
JREUtils.setupBridgeWindow(new Surface(texture));
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
if(surfaceReadyListener != null){
|
||||
surfaceReadyListener.isReady();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Tools.showError(getContext(), e, true);
|
||||
}
|
||||
}, "JVM Main thread").start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
|
||||
windowWidth = Tools.getDisplayFriendlyRes(width, scaleFactor);
|
||||
windowHeight = Tools.getDisplayFriendlyRes(height, scaleFactor);
|
||||
CallbackBridge.sendUpdateWindowSize(windowWidth, windowHeight);
|
||||
getMcScale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
|
||||
texture.setDefaultBufferSize(windowWidth, windowHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The touch event for both grabbed an non-grabbed mouse state on the touch screen
|
||||
* Does not cover the virtual mouse touchpad
|
||||
*/
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent e) {
|
||||
// Looking for a mouse to handle, won't have an effect if no mouse exists.
|
||||
for (int i = 0; i < e.getPointerCount(); i++) {
|
||||
if (e.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE) continue;
|
||||
|
||||
// Mouse found
|
||||
if(CallbackBridge.isGrabbing()) return false;
|
||||
CallbackBridge.sendCursorPos( e.getX(i) * scaleFactor, e.getY(i) * scaleFactor);
|
||||
return true; //mouse event handled successfully
|
||||
}
|
||||
|
||||
// System.out.println("Pre touch, isTouchInHotbar=" + Boolean.toString(isTouchInHotbar) + ", action=" + MotionEvent.actionToString(e.getActionMasked()));
|
||||
|
||||
//Getting scaled position from the event
|
||||
/* Tells if a double tap happened [MOUSE GRAB ONLY]. Doesn't tell where though. */
|
||||
if(!CallbackBridge.isGrabbing()) {
|
||||
mouse_x = (e.getX() * scaleFactor);
|
||||
mouse_y = (e.getY() * scaleFactor);
|
||||
//One android click = one MC click
|
||||
if(singleTapDetector.onTouchEvent(e)){
|
||||
CallbackBridge.putMouseEventWithCoords(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, mouse_x, mouse_y);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check double tap state, used for the hotbar
|
||||
boolean hasDoubleTapped = doubleTapDetector.onTouchEvent(e);
|
||||
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
int pointerCount = e.getPointerCount();
|
||||
|
||||
// In-menu interactions
|
||||
if(!CallbackBridge.isGrabbing()){
|
||||
|
||||
// Touch hover
|
||||
if(pointerCount == 1){
|
||||
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
|
||||
prevX = e.getX();
|
||||
prevY = e.getY();
|
||||
break;
|
||||
}
|
||||
|
||||
// Scrolling feature
|
||||
if(LauncherPreferences.PREF_DISABLE_GESTURES) break;
|
||||
// The pointer count can never be 0, and it is not 1, therefore it is >= 2
|
||||
int hScroll = ((int) (e.getX() - scrollLastInitialX)) / FINGER_SCROLL_THRESHOLD;
|
||||
int vScroll = ((int) (e.getY() - scrollLastInitialY)) / FINGER_SCROLL_THRESHOLD;
|
||||
|
||||
if(vScroll != 0 || hScroll != 0){
|
||||
CallbackBridge.sendScroll(hScroll, vScroll);
|
||||
scrollLastInitialX = e.getX();
|
||||
scrollLastInitialY = e.getY();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Camera movement
|
||||
int pointerIndex = e.findPointerIndex(currentPointerID);
|
||||
int hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
|
||||
// Start movement, due to new pointer or loss of pointer
|
||||
if (pointerIndex == -1 || lastPointerCount != pointerCount || !shouldBeDown) {
|
||||
if(hudKeyHandled != -1) break; //No pointer attribution on hotbar
|
||||
|
||||
shouldBeDown = true;
|
||||
currentPointerID = e.getPointerId(0);
|
||||
prevX = e.getX();
|
||||
prevY = e.getY();
|
||||
break;
|
||||
}
|
||||
// Continue movement as usual
|
||||
if(hudKeyHandled == -1){ //No camera on hotbar
|
||||
mouse_x += (e.getX(pointerIndex) - prevX) * sensitivityFactor;
|
||||
mouse_y += (e.getY(pointerIndex) - prevY) * sensitivityFactor;
|
||||
}
|
||||
|
||||
prevX = e.getX(pointerIndex);
|
||||
prevY = e.getY(pointerIndex);
|
||||
|
||||
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_DOWN: // 0
|
||||
CallbackBridge.sendPrepareGrabInitialPos();
|
||||
|
||||
hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
|
||||
boolean isTouchInHotbar = hudKeyHandled != -1;
|
||||
if (isTouchInHotbar) {
|
||||
sendKeyPress(hudKeyHandled);
|
||||
if(hasDoubleTapped && hudKeyHandled == lastHotbarKey){
|
||||
//Prevent double tapping Event on two different slots
|
||||
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_F);
|
||||
}
|
||||
|
||||
theHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 350);
|
||||
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
|
||||
lastHotbarKey = hudKeyHandled;
|
||||
break;
|
||||
}
|
||||
|
||||
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
|
||||
prevX = e.getX();
|
||||
prevY = e.getY();
|
||||
|
||||
if (CallbackBridge.isGrabbing()) {
|
||||
currentPointerID = e.getPointerId(0);
|
||||
// It cause hold left mouse while moving camera
|
||||
initialX = mouse_x;
|
||||
initialY = mouse_y;
|
||||
theHandler.sendEmptyMessageDelayed(MSG_LEFT_MOUSE_BUTTON_CHECK, LauncherPreferences.PREF_LONGPRESS_TRIGGER);
|
||||
}
|
||||
lastHotbarKey = hudKeyHandled;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP: // 1
|
||||
case MotionEvent.ACTION_CANCEL: // 3
|
||||
shouldBeDown = false;
|
||||
currentPointerID = -1;
|
||||
|
||||
hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
|
||||
isTouchInHotbar = hudKeyHandled != -1;
|
||||
// We only treat in world events
|
||||
if (!CallbackBridge.isGrabbing()) break;
|
||||
|
||||
// Stop the dropping of items
|
||||
if (isTouchInHotbar) {
|
||||
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_Q, 0, false);
|
||||
theHandler.removeMessages(MSG_DROP_ITEM_BUTTON_CHECK);
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove the mouse left button
|
||||
if(triggeredLeftMouseButton){
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, false);
|
||||
triggeredLeftMouseButton = false;
|
||||
break;
|
||||
}
|
||||
theHandler.removeMessages(MSG_LEFT_MOUSE_BUTTON_CHECK);
|
||||
|
||||
// In case of a short click, just send a quick right click
|
||||
if(!LauncherPreferences.PREF_DISABLE_GESTURES &&
|
||||
MathUtils.dist(initialX, initialY, mouse_x, mouse_y) < FINGER_STILL_THRESHOLD){
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, true);
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN: // 5
|
||||
//TODO Hey we could have some sort of middle click detection ?
|
||||
|
||||
scrollLastInitialX = e.getX();
|
||||
scrollLastInitialY = e.getY();
|
||||
//Checking if we are pressing the hotbar to select the item
|
||||
hudKeyHandled = handleGuiBar((int)e.getX(e.getPointerCount()-1), (int) e.getY(e.getPointerCount()-1));
|
||||
if(hudKeyHandled != -1){
|
||||
sendKeyPress(hudKeyHandled);
|
||||
if(hasDoubleTapped && hudKeyHandled == lastHotbarKey){
|
||||
//Prevent double tapping Event on two different slots
|
||||
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_F);
|
||||
}
|
||||
}
|
||||
|
||||
lastHotbarKey = hudKeyHandled;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// Actualise the pointer count
|
||||
lastPointerCount = e.getPointerCount();
|
||||
|
||||
//debugText.setText(CallbackBridge.DEBUG_STRING.toString());
|
||||
CallbackBridge.DEBUG_STRING.setLength(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The event for mouse/joystick movements
|
||||
* We don't do the gamepad right now.
|
||||
*/
|
||||
@Override
|
||||
public boolean dispatchGenericMotionEvent(MotionEvent event) {
|
||||
int mouseCursorIndex = -1;
|
||||
|
||||
if(Gamepad.isGamepadEvent(event)){
|
||||
if(gamepad == null){
|
||||
gamepad = new Gamepad(this, event.getDevice());
|
||||
}
|
||||
|
||||
gamepad.update(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
for(int i = 0; i < event.getPointerCount(); i++) {
|
||||
if(event.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE) continue;
|
||||
// Mouse found
|
||||
mouseCursorIndex = i;
|
||||
break;
|
||||
}
|
||||
if(mouseCursorIndex == -1) return false; // we cant consoom that, theres no mice!
|
||||
if(CallbackBridge.isGrabbing()) {
|
||||
if(BaseMainActivity.isAndroid8OrHigher()){
|
||||
requestFocus();
|
||||
requestPointerCapture();
|
||||
}
|
||||
}
|
||||
switch(event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
mouse_x = (event.getX(mouseCursorIndex) * scaleFactor);
|
||||
mouse_y = (event.getY(mouseCursorIndex) * scaleFactor);
|
||||
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
|
||||
//debugText.setText(CallbackBridge.DEBUG_STRING.toString());
|
||||
CallbackBridge.DEBUG_STRING.setLength(0);
|
||||
return true;
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
CallbackBridge.sendScroll((double) event.getAxisValue(MotionEvent.AXIS_VSCROLL), (double) event.getAxisValue(MotionEvent.AXIS_HSCROLL));
|
||||
return true;
|
||||
case MotionEvent.ACTION_BUTTON_PRESS:
|
||||
return sendMouseButtonUnconverted(event.getActionButton(),true);
|
||||
case MotionEvent.ACTION_BUTTON_RELEASE:
|
||||
return sendMouseButtonUnconverted(event.getActionButton(),false);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO MOVE THIS SOMEWHERE ELSE
|
||||
private boolean debugErrored = false;
|
||||
/** The input event for mouse with a captured pointer */
|
||||
@RequiresApi(26)
|
||||
@Override
|
||||
public boolean dispatchCapturedPointerEvent(MotionEvent e) {
|
||||
mouse_x += (e.getX()*scaleFactor);
|
||||
mouse_y += (e.getY()*scaleFactor);
|
||||
CallbackBridge.mouseX = mouse_x;
|
||||
CallbackBridge.mouseY = mouse_y;
|
||||
if(!CallbackBridge.isGrabbing()){
|
||||
releasePointerCapture();
|
||||
clearFocus();
|
||||
}
|
||||
|
||||
if (pointerDebugText.getVisibility() == View.VISIBLE && !debugErrored) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try {
|
||||
builder.append("PointerCapture debug\n");
|
||||
builder.append("MotionEvent=").append(e.getActionMasked()).append("\n");
|
||||
builder.append("PressingBtn=").append(MotionEvent.class.getDeclaredMethod("buttonStateToString").invoke(null, e.getButtonState())).append("\n\n");
|
||||
|
||||
builder.append("PointerX=").append(e.getX()).append("\n");
|
||||
builder.append("PointerY=").append(e.getY()).append("\n");
|
||||
builder.append("RawX=").append(e.getRawX()).append("\n");
|
||||
builder.append("RawY=").append(e.getRawY()).append("\n\n");
|
||||
|
||||
builder.append("XPos=").append(mouse_x).append("\n");
|
||||
builder.append("YPos=").append(mouse_y).append("\n\n");
|
||||
builder.append("MovingX=").append(getMoving(e.getX(), true)).append("\n");
|
||||
builder.append("MovingY=").append(getMoving(e.getY(), false)).append("\n");
|
||||
} catch (Throwable th) {
|
||||
debugErrored = true;
|
||||
builder.append("Error getting debug. The debug will be stopped!\n").append(Log.getStackTraceString(th));
|
||||
} finally {
|
||||
pointerDebugText.setText(builder.toString());
|
||||
builder.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
pointerDebugText.setText(CallbackBridge.DEBUG_STRING.toString());
|
||||
CallbackBridge.DEBUG_STRING.setLength(0);
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
|
||||
return true;
|
||||
case MotionEvent.ACTION_BUTTON_PRESS:
|
||||
return sendMouseButtonUnconverted(e.getActionButton(), true);
|
||||
case MotionEvent.ACTION_BUTTON_RELEASE:
|
||||
return sendMouseButtonUnconverted(e.getActionButton(), false);
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
CallbackBridge.sendScroll(e.getAxisValue(MotionEvent.AXIS_HSCROLL), e.getAxisValue(MotionEvent.AXIS_VSCROLL));
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** The event for keyboard/ gamepad button inputs */
|
||||
@Override
|
||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||
//Toast.makeText(this, event.toString(),Toast.LENGTH_SHORT).show();
|
||||
//Toast.makeText(this, event.getDevice().toString(), Toast.LENGTH_SHORT).show();
|
||||
|
||||
//Filtering useless events by order of probability
|
||||
if((event.getFlags() & KeyEvent.FLAG_FALLBACK) == KeyEvent.FLAG_FALLBACK) return true;
|
||||
int eventKeycode = event.getKeyCode();
|
||||
if(eventKeycode == KeyEvent.KEYCODE_UNKNOWN) return true;
|
||||
if(eventKeycode == KeyEvent.KEYCODE_VOLUME_DOWN) return false;
|
||||
if(eventKeycode == KeyEvent.KEYCODE_VOLUME_UP) return false;
|
||||
if(event.getRepeatCount() != 0) return true;
|
||||
if(event.getAction() == KeyEvent.ACTION_MULTIPLE) return true;
|
||||
|
||||
//Toast.makeText(this, "FIRST VERIF PASSED", Toast.LENGTH_SHORT).show();
|
||||
|
||||
//Sometimes, key events comes from SOME keys of the software keyboard
|
||||
//Even weirder, is is unknown why a key or another is selected to trigger a keyEvent
|
||||
if((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) == KeyEvent.FLAG_SOFT_KEYBOARD){
|
||||
if(eventKeycode == KeyEvent.KEYCODE_ENTER) return true; //We already listen to it.
|
||||
touchCharInput.dispatchKeyEvent(event);
|
||||
return true;
|
||||
}
|
||||
//Toast.makeText(this, "SECOND VERIF PASSED", Toast.LENGTH_SHORT).show();
|
||||
|
||||
|
||||
//Sometimes, key events may come from the mouse
|
||||
if(event.getDevice() != null
|
||||
&& ( (event.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE
|
||||
|| (event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ){
|
||||
//Toast.makeText(this, "THE EVENT COMES FROM A MOUSE", Toast.LENGTH_SHORT).show();
|
||||
|
||||
|
||||
if(eventKeycode == KeyEvent.KEYCODE_BACK){
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, event.getAction() == KeyEvent.ACTION_DOWN);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
System.out.println(event);
|
||||
|
||||
if(Gamepad.isGamepadEvent(event)){
|
||||
|
||||
if(gamepad == null){
|
||||
gamepad = new Gamepad(this, event.getDevice());
|
||||
}
|
||||
|
||||
gamepad.update(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
int index = EfficientAndroidLWJGLKeycode.getIndexByKey(eventKeycode);
|
||||
if(index >= 0) {
|
||||
//Toast.makeText(this,"THIS IS A KEYBOARD EVENT !", Toast.LENGTH_SHORT).show();
|
||||
EfficientAndroidLWJGLKeycode.execKey(event, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Get the mouse direction as a string */
|
||||
private String getMoving(float pos, boolean xOrY) {
|
||||
if (pos == 0) return "STOPPED";
|
||||
if (pos > 0) return xOrY ? "RIGHT" : "DOWN";
|
||||
return xOrY ? "LEFT" : "UP";
|
||||
}
|
||||
|
||||
/** Convert the mouse button, then send it
|
||||
* @return Whether the event was processed
|
||||
*/
|
||||
public static boolean sendMouseButtonUnconverted(int button, boolean status) {
|
||||
int glfwButton = -256;
|
||||
switch (button) {
|
||||
case MotionEvent.BUTTON_PRIMARY:
|
||||
glfwButton = LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT;
|
||||
break;
|
||||
case MotionEvent.BUTTON_TERTIARY:
|
||||
glfwButton = LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_MIDDLE;
|
||||
break;
|
||||
case MotionEvent.BUTTON_SECONDARY:
|
||||
glfwButton = LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT;
|
||||
break;
|
||||
}
|
||||
if(glfwButton == -256) return false;
|
||||
sendMouseButton(glfwButton, status);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @return the hotbar key, given the position. -1 if no key are pressed */
|
||||
public int handleGuiBar(int x, int y) {
|
||||
if (!CallbackBridge.isGrabbing()) return -1;
|
||||
|
||||
int barHeight = mcscale(20);
|
||||
int barY = CallbackBridge.physicalHeight - barHeight;
|
||||
if(y < barY) return -1;
|
||||
|
||||
int barWidth = mcscale(180);
|
||||
int barX = (CallbackBridge.physicalWidth / 2) - (barWidth / 2);
|
||||
if(x < barX || x >= barX + barWidth) return -1;
|
||||
|
||||
return hotbarKeys[(int) net.kdt.pojavlaunch.utils.MathUtils.map(x, barX, barX + barWidth, 0, 9)];
|
||||
}
|
||||
|
||||
/** Return the size, given the UI scale size */
|
||||
private int mcscale(int input) {
|
||||
return (int)((GUIScale * input)/scaleFactor);
|
||||
}
|
||||
|
||||
/** Toggle the pointerDebugText visibility state */
|
||||
public void togglepointerDebugging() {
|
||||
pointerDebugText.setVisibility(pointerDebugText.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/** A small interface called when the listener is ready for the first time */
|
||||
public interface SurfaceReadyListener {
|
||||
void isReady();
|
||||
}
|
||||
|
||||
public void setSurfaceReadyListener(SurfaceReadyListener listener){
|
||||
surfaceReadyListener = listener;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.content.pm.*;
|
||||
import android.content.res.*;
|
||||
import android.os.*;
|
||||
import androidx.core.app.*;
|
||||
|
||||
import android.util.*;
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
|
||||
public class PojavApplication extends Application
|
||||
{
|
||||
public static String CRASH_REPORT_TAG = "PojavCrashReport";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable th) {
|
||||
boolean storagePermAllowed = Build.VERSION.SDK_INT < 23 || ActivityCompat.checkSelfPermission(PojavApplication.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
File crashFile = new File(storagePermAllowed ? Tools.DIR_GAME_HOME : Tools.DIR_DATA, "latestcrash.txt");
|
||||
try {
|
||||
// Write to file, since some devices may not able to show error
|
||||
crashFile.getParentFile().mkdirs();
|
||||
crashFile.createNewFile();
|
||||
PrintStream crashStream = new PrintStream(crashFile);
|
||||
crashStream.append("PojavLauncher crash report\n");
|
||||
crashStream.append(" - Time: " + DateFormat.getDateTimeInstance().format(new Date()) + "\n");
|
||||
crashStream.append(" - Device: " + Build.PRODUCT + " " + Build.MODEL + "\n");
|
||||
crashStream.append(" - Android version: " + Build.VERSION.RELEASE + "\n");
|
||||
crashStream.append(" - Crash stack trace:\n");
|
||||
crashStream.append(" - Launcher version: " + BuildConfig.VERSION_NAME + "\n");
|
||||
crashStream.append(Log.getStackTraceString(th));
|
||||
crashStream.close();
|
||||
} catch (Throwable th2) {
|
||||
Log.e(CRASH_REPORT_TAG, " - Exception attempt saving crash stack trace:", th2);
|
||||
Log.e(CRASH_REPORT_TAG, " - The crash stack trace was:", th);
|
||||
}
|
||||
|
||||
FatalErrorActivity.showError(PojavApplication.this, crashFile.getAbsolutePath(), storagePermAllowed, th);
|
||||
// android.os.Process.killProcess(android.os.Process.myPid());
|
||||
|
||||
BaseMainActivity.fullyExit();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
super.onCreate();
|
||||
Tools.APP_NAME = getResources().getString(R.string.app_short_name);
|
||||
|
||||
Tools.DIR_DATA = getDir("files", MODE_PRIVATE).getParent();
|
||||
//Tools.DIR_HOME_JRE = Tools.DIR_DATA + "/jre_runtime".replace("/data/user/0", "/data/data");
|
||||
Tools.DIR_ACCOUNT_OLD = Tools.DIR_DATA + "/Users";
|
||||
Tools.DIR_ACCOUNT_NEW = Tools.DIR_DATA + "/accounts";
|
||||
// Tools.FILE_ACCOUNT_JSON = getFilesDir().getAbsolutePath() + "/account_profiles.json";
|
||||
|
||||
|
||||
Tools.DEVICE_ARCHITECTURE = Architecture.getDeviceArchitecture();
|
||||
//Force x86 lib directory for Asus x86 based zenfones
|
||||
if(Architecture.isx86Device() && Architecture.is32BitsDevice()){
|
||||
String originalJNIDirectory = getApplicationInfo().nativeLibraryDir;
|
||||
getApplicationInfo().nativeLibraryDir = originalJNIDirectory.substring(0,
|
||||
originalJNIDirectory.lastIndexOf("/"))
|
||||
.concat("/x86");
|
||||
}
|
||||
|
||||
|
||||
} catch (Throwable th) {
|
||||
Intent ferrorIntent = new Intent(this, FatalErrorActivity.class);
|
||||
ferrorIntent.putExtra("throwable", th);
|
||||
startActivity(ferrorIntent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(LocaleUtils.setLocale(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
LocaleUtils.setLocale(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,392 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
|
||||
import static android.os.Build.VERSION_CODES.P;
|
||||
import static net.kdt.pojavlaunch.Tools.getFileName;
|
||||
import static net.kdt.pojavlaunch.Tools.ignoreNotch;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_HIDE_SIDEBAR;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.Guideline;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import net.kdt.pojavlaunch.extra.ExtraCore;
|
||||
import net.kdt.pojavlaunch.extra.ExtraListener;
|
||||
import net.kdt.pojavlaunch.fragments.ConsoleFragment;
|
||||
import net.kdt.pojavlaunch.fragments.CrashFragment;
|
||||
import net.kdt.pojavlaunch.fragments.LauncherFragment;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.prefs.screens.LauncherPreferenceFragment;
|
||||
import net.kdt.pojavlaunch.value.MinecraftAccount;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PojavLauncherActivity extends BaseLauncherActivity
|
||||
{
|
||||
|
||||
// An equivalent ViewPager2 adapter class
|
||||
private static class ScreenSlidePagerAdapter extends FragmentStateAdapter {
|
||||
public ScreenSlidePagerAdapter(FragmentActivity fa) {
|
||||
super(fa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
if (position == 0) return new LauncherFragment();
|
||||
if (position == 1) return new ConsoleFragment();
|
||||
if (position == 2) return new CrashFragment();
|
||||
if (position == 3) return new LauncherPreferenceFragment();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private TextView tvConnectStatus;
|
||||
private Spinner accountSelector;
|
||||
private ViewPager2 viewPager;
|
||||
private final Button[] Tabs = new Button[4];
|
||||
private View selectedTab;
|
||||
private ImageView accountFaceImageView;
|
||||
|
||||
private Button logoutBtn; // MineButtons
|
||||
private ExtraListener backPreferenceListener;
|
||||
|
||||
public PojavLauncherActivity() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.launcher_main_v4);
|
||||
|
||||
if (true) {
|
||||
final ProgressDialog barrier = new ProgressDialog(this);
|
||||
barrier.setMessage(getString(R.string.global_waiting));
|
||||
barrier.setProgressStyle(barrier.STYLE_SPINNER);
|
||||
barrier.setCancelable(false);
|
||||
barrier.show();
|
||||
|
||||
//final Uri uri = data.getData();
|
||||
Uri uri = Uri.parse("android.resource://"+getPackageName()+"/"+R.raw.beta);
|
||||
String path = uri.getPath();
|
||||
//File modFile = new File(new URI(path));
|
||||
|
||||
|
||||
barrier.setMessage(PojavLauncherActivity.this.getString(R.string.multirt_progress_caching));
|
||||
Thread t = new Thread(()->{
|
||||
try {
|
||||
final String name = getFileName(this, uri);
|
||||
final File modInstallerFile = new File(getCacheDir(), name);
|
||||
FileOutputStream fos = new FileOutputStream(modInstallerFile);
|
||||
IOUtils.copy(getContentResolver().openInputStream(uri), fos);
|
||||
fos.close();
|
||||
PojavLauncherActivity.this.runOnUiThread(() -> {
|
||||
barrier.dismiss();
|
||||
Intent intent = new Intent(PojavLauncherActivity.this, JavaGUILauncherActivity.class);
|
||||
intent.putExtra("modFile", modInstallerFile);
|
||||
startActivity(intent);
|
||||
});
|
||||
}catch(IOException e) {
|
||||
Tools.showError(PojavLauncherActivity.this,e);
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
|
||||
//Boilerplate linking/initialisation
|
||||
/*
|
||||
viewPager = findViewById(R.id.launchermainTabPager);
|
||||
selectedTab = findViewById(R.id.viewTabSelected);
|
||||
tvConnectStatus = findViewById(R.id.launchermain_text_accountstatus);
|
||||
accountFaceImageView = findViewById(R.id.launchermain_account_image);
|
||||
accountSelector = findViewById(R.id.launchermain_spinner_account);
|
||||
mVersionSelector = findViewById(R.id.launchermain_spinner_version);
|
||||
mLaunchProgress = findViewById(R.id.progressDownloadBar);
|
||||
mLaunchTextStatus = findViewById(R.id.progressDownloadText);
|
||||
logoutBtn = findViewById(R.id.installJarButton);
|
||||
mPlayButton = findViewById(R.id.launchermainPlayButton);
|
||||
Tabs[0] = findViewById(R.id.btnTab1);
|
||||
Tabs[1] = findViewById(R.id.btnTab2);
|
||||
Tabs[2] = findViewById(R.id.btnTab3);
|
||||
Tabs[3] = findViewById(R.id.btnTab4);
|
||||
*/
|
||||
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Toast.makeText(this, "Launcher process id: " + android.os.Process.myPid(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
// Setup the viewPager to slide across fragments
|
||||
viewPager.setAdapter(new ScreenSlidePagerAdapter(this));
|
||||
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
setTabActive(position);
|
||||
}
|
||||
});
|
||||
initTabs(0);
|
||||
|
||||
//Setup listener to the backPreference system
|
||||
backPreferenceListener = (key, value) -> {
|
||||
if(value.equals("true")){
|
||||
onBackPressed();
|
||||
ExtraCore.setValue(key, "false");
|
||||
}
|
||||
return false;
|
||||
};
|
||||
ExtraCore.addExtraListener("back_preference", backPreferenceListener);
|
||||
|
||||
|
||||
// Try to load the temporary account
|
||||
final List<String> accountList = new ArrayList<>();
|
||||
final MinecraftAccount tempProfile = PojavProfile.getTempProfileContent();
|
||||
if (tempProfile != null) {
|
||||
accountList.add(tempProfile.username);
|
||||
}
|
||||
for (String s : new File(Tools.DIR_ACCOUNT_NEW).list()) {
|
||||
accountList.add(s.substring(0, s.length() - 5));
|
||||
}
|
||||
|
||||
// Setup account spinner
|
||||
pickAccount();
|
||||
ArrayAdapter<String> adapterAcc = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, accountList);
|
||||
adapterAcc.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
|
||||
accountSelector.setAdapter(adapterAcc);
|
||||
|
||||
if (tempProfile != null) {
|
||||
accountSelector.setSelection(0);
|
||||
} else {
|
||||
for (int i = 0; i < accountList.size(); i++) {
|
||||
String account = accountList.get(i);
|
||||
if (account.equals(mProfile.username)) {
|
||||
accountSelector.setSelection(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accountSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> p1, View p2, int position, long p4) {
|
||||
if (tempProfile != null && position == 0) {
|
||||
PojavProfile.setCurrentProfile(PojavLauncherActivity.this, tempProfile);
|
||||
} else {
|
||||
PojavProfile.setCurrentProfile(PojavLauncherActivity.this,
|
||||
accountList.get(position + (tempProfile != null ? 1 : 0)));
|
||||
}
|
||||
pickAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> p1) {
|
||||
// TODO: Implement this method
|
||||
}
|
||||
});
|
||||
|
||||
// Setup the minecraft version list
|
||||
List<String> versions = new ArrayList<>();
|
||||
final File fVers = new File(Tools.DIR_HOME_VERSION);
|
||||
|
||||
try {
|
||||
if (fVers.listFiles().length < 1) {
|
||||
throw new Exception(getString(R.string.error_no_version));
|
||||
}
|
||||
|
||||
for (File fVer : fVers.listFiles()) {
|
||||
if (fVer.isDirectory())
|
||||
versions.add(fVer.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
versions.add(getString(R.string.global_error) + ":");
|
||||
versions.add(e.getMessage());
|
||||
|
||||
} finally {
|
||||
mAvailableVersions = versions.toArray(new String[0]);
|
||||
}
|
||||
|
||||
//mAvailableVersions;
|
||||
ArrayAdapter<String> adapterVer = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, mAvailableVersions);
|
||||
adapterVer.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
|
||||
mVersionSelector.setAdapter(adapterVer);
|
||||
|
||||
statusIsLaunching(false);
|
||||
|
||||
|
||||
//Add the preference changed listener
|
||||
LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener((sharedPreferences, key) -> {
|
||||
if(key.equals("hideSidebar")){
|
||||
changeLookAndFeel(sharedPreferences.getBoolean("hideSidebar",false));
|
||||
return;
|
||||
}
|
||||
|
||||
if(key.equals("ignoreNotch")){
|
||||
ignoreNotch(sharedPreferences.getBoolean("ignoreNotch", true), PojavLauncherActivity.this);
|
||||
return;
|
||||
}
|
||||
});
|
||||
changeLookAndFeel(PREF_HIDE_SIDEBAR);
|
||||
}
|
||||
|
||||
private void selectTabPage(int pageIndex){
|
||||
viewPager.setCurrentItem(pageIndex);
|
||||
setTabActive(pageIndex);
|
||||
}
|
||||
|
||||
private void pickAccount() {
|
||||
try {
|
||||
mProfile = PojavProfile.getCurrentProfileContent(this);
|
||||
accountFaceImageView.setImageBitmap(mProfile.getSkinFace());
|
||||
|
||||
//TODO FULL BACKGROUND LOGIN
|
||||
tvConnectStatus.setText(mProfile.accessToken.equals("0") ? R.string.mcl_account_offline : R.string.mcl_account_connected);
|
||||
} catch(Exception e) {
|
||||
mProfile = new MinecraftAccount();
|
||||
Tools.showError(this, e, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void statusIsLaunching(boolean isLaunching) {
|
||||
int launchVisibility = isLaunching ? View.VISIBLE : View.GONE;
|
||||
mLaunchProgress.setVisibility(launchVisibility);
|
||||
mLaunchTextStatus.setVisibility(launchVisibility);
|
||||
|
||||
|
||||
logoutBtn.setEnabled(!isLaunching);
|
||||
mVersionSelector.setEnabled(!isLaunching);
|
||||
canBack = !isLaunching;
|
||||
}
|
||||
|
||||
public void onTabClicked(View view) {
|
||||
for(int i=0; i<Tabs.length;i++){
|
||||
if(view.getId() == Tabs[i].getId()) {
|
||||
selectTabPage(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setTabActive(int index){
|
||||
for (Button tab : Tabs) {
|
||||
tab.setTypeface(null, Typeface.NORMAL);
|
||||
tab.setTextColor(Color.rgb(220,220,220)); //Slightly less bright white.
|
||||
}
|
||||
Tabs[index].setTypeface(Tabs[index].getTypeface(), Typeface.BOLD);
|
||||
Tabs[index].setTextColor(Color.WHITE);
|
||||
|
||||
//Animating the white bar on the left
|
||||
ValueAnimator animation = ValueAnimator.ofFloat(selectedTab.getY(), Tabs[index].getY()+(Tabs[index].getHeight()- selectedTab.getHeight())/2f);
|
||||
animation.setDuration(250);
|
||||
animation.addUpdateListener(animation1 -> selectedTab.setY((float) animation1.getAnimatedValue()));
|
||||
animation.start();
|
||||
}
|
||||
|
||||
protected void initTabs(int activeTab){
|
||||
final Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(() -> {
|
||||
//Do something after 100ms
|
||||
selectTabPage(activeTab);
|
||||
});
|
||||
}
|
||||
|
||||
private void changeLookAndFeel(boolean useOldLook){
|
||||
Guideline guideLine = findViewById(R.id.guidelineLeft);
|
||||
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) guideLine.getLayoutParams();
|
||||
|
||||
if(useOldLook || getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
//UI v1 Style
|
||||
//Hide the sidebar
|
||||
params.guidePercent = 0; // 0%, range: 0 <-> 1
|
||||
guideLine.setLayoutParams(params);
|
||||
|
||||
//Remove the selected Tab and the head image
|
||||
selectedTab.setVisibility(View.GONE);
|
||||
accountFaceImageView.setVisibility(View.GONE);
|
||||
|
||||
//Enlarge the button, but just a bit.
|
||||
params = (ConstraintLayout.LayoutParams) mPlayButton.getLayoutParams();
|
||||
params.matchConstraintPercentWidth = 0.35f;
|
||||
}else{
|
||||
//UI v2 Style
|
||||
//Show the sidebar back
|
||||
params.guidePercent = 0.23f; // 23%, range: 0 <-> 1
|
||||
guideLine.setLayoutParams(params);
|
||||
|
||||
//Show the selected Tab
|
||||
selectedTab.setVisibility(View.VISIBLE);
|
||||
accountFaceImageView.setVisibility(View.VISIBLE);
|
||||
|
||||
//Set the default button size
|
||||
params = (ConstraintLayout.LayoutParams) mPlayButton.getLayoutParams();
|
||||
params.matchConstraintPercentWidth = 0.25f;
|
||||
}
|
||||
mPlayButton.setLayoutParams(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
//Try to get the notch so it can be taken into account in settings
|
||||
if (Build.VERSION.SDK_INT >= P){
|
||||
try {
|
||||
PREF_NOTCH_SIZE = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout().getBoundingRects().get(0).width();
|
||||
}catch (Exception e){
|
||||
Log.i("NOTCH DETECTION", "No notch detected, or the device if in split screen mode");
|
||||
PREF_NOTCH_SIZE = -1;
|
||||
}
|
||||
Tools.updateWindowSize(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom back stack system. Use the classic backstack when the focus is on the setting screen,
|
||||
* finish the activity and remove the back_preference listener otherwise
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
int count = getSupportFragmentManager().getBackStackEntryCount();
|
||||
|
||||
if(count > 0 && viewPager.getCurrentItem() == 3){
|
||||
getSupportFragmentManager().popBackStack();
|
||||
}else{
|
||||
super.onBackPressed();
|
||||
//additional code
|
||||
ExtraCore.removeExtraListenerFromValue("back_preference", backPreferenceListener);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
import android.content.*;
|
||||
import java.io.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
public class PojavMigrator
|
||||
{
|
||||
public static void migrateAccountData(Context ctx) {
|
||||
File oldAccDir = new File(Tools.DIR_ACCOUNT_OLD);
|
||||
if (oldAccDir.exists() && oldAccDir.isDirectory()) {
|
||||
for (String account : oldAccDir.list()) {
|
||||
File oldAccFile = new File(oldAccDir, account);
|
||||
|
||||
try {
|
||||
MCProfile.Builder oldAccStruct = MCProfile.load(oldAccFile.getAbsolutePath());
|
||||
|
||||
MinecraftAccount newAccStruct = new MinecraftAccount();
|
||||
newAccStruct.accessToken = oldAccStruct.getAccessToken();
|
||||
newAccStruct.clientToken = oldAccStruct.getClientID();
|
||||
newAccStruct.isMicrosoft = false;
|
||||
newAccStruct.profileId = oldAccStruct.getProfileID();
|
||||
newAccStruct.selectedVersion = oldAccStruct.getVersion();
|
||||
newAccStruct.username = oldAccStruct.getUsername();
|
||||
newAccStruct.save();
|
||||
} catch (IOException e) {
|
||||
Tools.showError(ctx, e);
|
||||
}
|
||||
|
||||
oldAccFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean migrateGameDir() throws IOException {
|
||||
File oldGameDir = new File(Tools.DIR_GAME_OLD);
|
||||
|
||||
boolean moved = oldGameDir.exists() && oldGameDir.isDirectory();
|
||||
/*
|
||||
if (!migrateBugFix20201217() && moved) {
|
||||
command("mv " + Tools.DIR_GAME_OLD + " " + Tools.DIR_GAME_HOME + "/");
|
||||
}
|
||||
*/
|
||||
if(moved) {
|
||||
oldGameDir.renameTo(new File(Tools.DIR_GAME_NEW + "/"));
|
||||
FileUtils.deleteDirectory(new File(Tools.DIR_GAME_NEW + "/lwjgl3"));
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
|
||||
private static void command(String cmd) throws IOException, InterruptedException {
|
||||
Process p = Runtime.getRuntime().exec(cmd);
|
||||
int exitCode = p.waitFor();
|
||||
if (exitCode != 0) {
|
||||
throw new IOException("Exit code " + exitCode +
|
||||
", message:\n" + Tools.read(p.getErrorStream()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
package net.kdt.pojavlaunch;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.RefreshListener;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.RefreshTokenTask;
|
||||
import net.kdt.pojavlaunch.value.MinecraftAccount;
|
||||
|
||||
public class PojavProfile
|
||||
{
|
||||
private static String PROFILE_PREF = "pojav_profile";
|
||||
private static String PROFILE_PREF_FILE = "file";
|
||||
public static String PROFILE_PREF_TEMP_CONTENT = "tempContent";
|
||||
|
||||
public static SharedPreferences getPrefs(Context ctx) {
|
||||
return ctx.getSharedPreferences(PROFILE_PREF, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static MinecraftAccount getCurrentProfileContent(Context ctx) throws JsonSyntaxException {
|
||||
MinecraftAccount build = MinecraftAccount.load(getCurrentProfileName(ctx));
|
||||
if (build == null) {
|
||||
System.out.println("isTempProfile null? " + (getTempProfileContent() == null));
|
||||
return getTempProfileContent();
|
||||
}
|
||||
return build;
|
||||
}
|
||||
|
||||
public static MinecraftAccount getTempProfileContent() {
|
||||
try {
|
||||
MinecraftAccount acc = MinecraftAccount.parse(Tools.read(Tools.DIR_DATA+"/cache/tempacc.json"));
|
||||
if (acc.accessToken == null) {
|
||||
acc.accessToken = "0";
|
||||
}
|
||||
if (acc.clientToken == null) {
|
||||
acc.clientToken = "0";
|
||||
}
|
||||
if (acc.profileId == null) {
|
||||
acc.profileId = "00000000-0000-0000-0000-000000000000";
|
||||
}
|
||||
if (acc.username == null) {
|
||||
acc.username = "0";
|
||||
}
|
||||
if (acc.selectedVersion == null) {
|
||||
acc.selectedVersion = "1.7.10";
|
||||
}
|
||||
if (acc.msaRefreshToken == null) {
|
||||
acc.msaRefreshToken = "0";
|
||||
}
|
||||
return acc;
|
||||
}catch (IOException e) {
|
||||
Log.e(MinecraftAccount.class.getName(), "Caught an exception while loading the temporary profile",e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getCurrentProfileName(Context ctx) {
|
||||
String name = getPrefs(ctx).getString(PROFILE_PREF_FILE, "");
|
||||
// A dirty fix
|
||||
if (!name.isEmpty() && name.startsWith(Tools.DIR_ACCOUNT_NEW) && name.endsWith(".json")) {
|
||||
name = name.substring(0, name.length() - 5).replace(Tools.DIR_ACCOUNT_NEW, "").replace(".json", "");
|
||||
setCurrentProfile(ctx, name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public static boolean setCurrentProfile(Context ctx, Object obj) {
|
||||
SharedPreferences.Editor pref = getPrefs(ctx).edit();
|
||||
|
||||
try {
|
||||
if (obj instanceof MinecraftAccount) {
|
||||
try {
|
||||
MinecraftAccount.saveTempAccount((MinecraftAccount) obj);
|
||||
} catch (IOException e) {
|
||||
Tools.showError(ctx, e);
|
||||
}
|
||||
} else if (obj instanceof String) {
|
||||
String acc = (String) obj;
|
||||
pref.putString(PROFILE_PREF_FILE, acc);
|
||||
MinecraftAccount.clearTempAccount();
|
||||
} else if (obj == null) {
|
||||
pref.putString(PROFILE_PREF_FILE, "");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Profile must be MinecraftAccount.class, String.class or null");
|
||||
}
|
||||
} finally {
|
||||
return pref.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isFileType(Context ctx) {
|
||||
return new File(Tools.DIR_ACCOUNT_NEW + "/" + PojavProfile.getCurrentProfileName(ctx) + ".json").exists();
|
||||
}
|
||||
|
||||
|
||||
public static void launch(Activity ctx, Object o) {
|
||||
PojavProfile.setCurrentProfile(ctx, o);
|
||||
|
||||
Intent intent = new Intent(ctx, PojavLauncherActivity.class); //MCLauncherActivity.class);
|
||||
ctx.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void updateTokens(final Activity ctx, final String name, RefreshListener listen) throws Exception {
|
||||
new RefreshTokenTask(ctx, listen).execute(name);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.microsoft;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.*;
|
||||
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
public class MicrosoftAuthTask extends AsyncTask<String, Void, Object> {
|
||||
/*
|
||||
private static final String authTokenUrl = "https://login.live.com/oauth20_token.srf";
|
||||
private static final String xblAuthUrl = "https://user.auth.xboxlive.com/user/authenticate";
|
||||
private static final String xstsAuthUrl = "https://xsts.auth.xboxlive.com/xsts/authorize";
|
||||
private static final String mcLoginUrl = "https://api.minecraftservices.com/authentication/login_with_xbox";
|
||||
private static final String mcStoreUrl = "https://api.minecraftservices.com/entitlements/mcstore";
|
||||
private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile";
|
||||
*/
|
||||
|
||||
//private Gson gson = new Gson();
|
||||
private final RefreshListener listener;
|
||||
|
||||
private final WeakReference<Context> ctx;
|
||||
private ProgressDialog build;
|
||||
|
||||
public MicrosoftAuthTask(Context ctx, RefreshListener listener) {
|
||||
this.ctx = new WeakReference<>(ctx);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreExecute() {
|
||||
build = new ProgressDialog(ctx.get());
|
||||
build.setMessage(ctx.get().getString(R.string.global_waiting));
|
||||
build.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
build.setCancelable(false);
|
||||
build.setMax(6);
|
||||
build.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doInBackground(String... args) {
|
||||
try {
|
||||
/*
|
||||
publishProgress();
|
||||
String msaAccessToken = acquireAccessToken(args[0]);
|
||||
|
||||
publishProgress();
|
||||
String xblToken = acquireXBLToken(msaAccessToken);
|
||||
|
||||
publishProgress();
|
||||
String[] xstsData = acquireXsts(xblToken);
|
||||
|
||||
publishProgress();
|
||||
String mcAccessToken = acquireMinecraftToken(xstsData[0], xstsData[1]);
|
||||
|
||||
publishProgress();
|
||||
|
||||
*/
|
||||
Msa msa = new Msa(this, Boolean.parseBoolean(args[0]), args[1]);
|
||||
|
||||
MinecraftAccount acc = MinecraftAccount.load(msa.mcName);
|
||||
if (msa.doesOwnGame) {
|
||||
acc.clientToken = "0"; /* FIXME */
|
||||
acc.accessToken = msa.mcToken;
|
||||
acc.username = msa.mcName;
|
||||
acc.profileId = msa.mcUuid;
|
||||
acc.isMicrosoft = true;
|
||||
acc.msaRefreshToken = msa.msRefreshToken;
|
||||
acc.updateSkinFace();
|
||||
}
|
||||
acc.save();
|
||||
|
||||
return acc;
|
||||
} catch (Throwable e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
public void publishProgressPublic() {
|
||||
super.publishProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Void[] p1) {
|
||||
super.onProgressUpdate(p1);
|
||||
build.setProgress(build.getProgress() + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Object result) {
|
||||
build.dismiss();
|
||||
if (result instanceof MinecraftAccount) {
|
||||
listener.onSuccess((MinecraftAccount) result);
|
||||
} else {
|
||||
listener.onFailed((Throwable) result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,296 +0,0 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package net.kdt.pojavlaunch.authenticator.microsoft;
|
||||
|
||||
import android.util.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import org.json.*;
|
||||
|
||||
|
||||
public class Msa {
|
||||
private static final String authTokenUrl = "https://login.live.com/oauth20_token.srf";
|
||||
private static final String xblAuthUrl = "https://user.auth.xboxlive.com/user/authenticate";
|
||||
private static final String xstsAuthUrl = "https://xsts.auth.xboxlive.com/xsts/authorize";
|
||||
private static final String mcLoginUrl = "https://api.minecraftservices.com/authentication/login_with_xbox";
|
||||
private static final String mcStoreUrl = "https://api.minecraftservices.com/entitlements/mcstore";
|
||||
private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile";
|
||||
|
||||
private MicrosoftAuthTask task;
|
||||
|
||||
public boolean isRefresh;
|
||||
public String msRefreshToken;
|
||||
public String mcName;
|
||||
public String mcToken;
|
||||
public String mcUuid;
|
||||
public boolean doesOwnGame;
|
||||
|
||||
public Msa(MicrosoftAuthTask task, boolean isRefresh, String authCode) throws IOException, JSONException {
|
||||
this.task = task;
|
||||
acquireAccessToken(isRefresh, authCode);
|
||||
}
|
||||
|
||||
public void acquireAccessToken(boolean isRefresh, String authcode) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(authTokenUrl);
|
||||
Log.i("MicroAuth", "isRefresh=" + isRefresh + ", authCode= "+authcode);
|
||||
Map<Object, Object> data = new HashMap<>();
|
||||
/*Map.of(
|
||||
"client_id", "00000000402b5328",
|
||||
"code", authcode,
|
||||
"grant_type", "authorization_code",
|
||||
"redirect_uri", "https://login.live.com/oauth20_desktop.srf",
|
||||
"scope", "service::user.auth.xboxlive.com::MBI_SSL"
|
||||
);*/
|
||||
data.put("client_id", "00000000402b5328");
|
||||
data.put(isRefresh ? "refresh_token" : "code", authcode);
|
||||
data.put("grant_type", isRefresh ? "refresh_token" : "authorization_code");
|
||||
data.put("redirect_url", "https://login.live.com/oauth20_desktop.srf");
|
||||
data.put("scope", "service::user.auth.xboxlive.com::MBI_SSL");
|
||||
|
||||
//да пошла yf[eq1 она ваша джава 11
|
||||
String req = ofFormData(data);
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
conn.setRequestProperty("charset", "utf-8");
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setUseCaches(false);
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.connect();
|
||||
try(OutputStream wr = conn.getOutputStream()) {
|
||||
wr.write(req.getBytes("UTF-8"));
|
||||
}
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
|
||||
msRefreshToken = jo.getString("refresh_token");
|
||||
Log.i("MicroAuth","Acess Token = "+jo.getString("access_token"));
|
||||
acquireXBLToken(jo.getString("access_token"));
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void acquireXBLToken(String accessToken) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(xblAuthUrl);
|
||||
|
||||
Map<Object, Object> data = new HashMap<>();
|
||||
Map<Object, Object> properties = new HashMap<>();
|
||||
properties.put("AuthMethod", "RPS");
|
||||
properties.put("SiteName", "user.auth.xboxlive.com");
|
||||
properties.put("RpsTicket", accessToken);
|
||||
data.put("Properties",properties);
|
||||
data.put("RelyingParty", "http://auth.xboxlive.com");
|
||||
data.put("TokenType", "JWT");
|
||||
/*Map.of(
|
||||
|
||||
"Properties", Map.of(
|
||||
"AuthMethod", "RPS",
|
||||
"SiteName", "user.auth.xboxlive.com",
|
||||
"RpsTicket", accessToken
|
||||
),
|
||||
"RelyingParty", "http://auth.xboxlive.com",
|
||||
"TokenType", "JWT"
|
||||
);*/
|
||||
String req = ofJSONData(data);
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("charset", "utf-8");
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setUseCaches(false);
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.connect();
|
||||
try(OutputStream wr = conn.getOutputStream()) {
|
||||
wr.write(req.getBytes("UTF-8"));
|
||||
}
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
|
||||
Log.i("MicroAuth","Xbl Token = "+jo.getString("Token"));
|
||||
acquireXsts(jo.getString("Token"));
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void acquireXsts(String xblToken) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(xstsAuthUrl);
|
||||
Map<Object, Object> data = new HashMap<>();
|
||||
Map<Object, Object> properties = new HashMap<>();
|
||||
properties.put("SandboxId", "RETAIL");
|
||||
properties.put("UserTokens",Collections.singleton(xblToken));
|
||||
data.put("Properties",properties);
|
||||
data.put("RelyingParty", "rp://api.minecraftservices.com/");
|
||||
data.put("TokenType", "JWT");
|
||||
/*Map<Object, Object> data = Map.of(
|
||||
"Properties", Map.of(
|
||||
"SandboxId", "RETAIL",
|
||||
"UserTokens", List.of(xblToken)
|
||||
),
|
||||
"RelyingParty", "rp://api.minecraftservices.com/",
|
||||
"TokenType", "JWT"
|
||||
);
|
||||
*/
|
||||
String req = ofJSONData(data);
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("charset", "utf-8");
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setUseCaches(false);
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.connect();
|
||||
try(OutputStream wr = conn.getOutputStream()) {
|
||||
wr.write(req.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
|
||||
String uhs = jo.getJSONObject("DisplayClaims").getJSONArray("xui").getJSONObject(0).getString("uhs");
|
||||
Log.i("MicroAuth","Xbl Xsts = "+jo.getString("Token")+"; Uhs = " + uhs);
|
||||
acquireMinecraftToken(uhs,jo.getString("Token"));
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void acquireMinecraftToken(String xblUhs, String xblXsts) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(mcLoginUrl);
|
||||
|
||||
Map<Object, Object> data = new HashMap<>();
|
||||
data.put("identityToken", "XBL3.0 x=" + xblUhs + ";" + xblXsts);
|
||||
|
||||
String req = ofJSONData(data);
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("charset", "utf-8");
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setUseCaches(false);
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.connect();
|
||||
try(OutputStream wr = conn.getOutputStream()) {
|
||||
wr.write(req.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
|
||||
Log.i("MicroAuth","MC token: "+jo.getString("access_token"));
|
||||
mcToken = jo.getString("access_token");
|
||||
checkMcProfile(jo.getString("access_token"));
|
||||
checkMcStore(jo.getString("access_token"));
|
||||
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
}
|
||||
private void checkMcStore(String mcAccessToken) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(mcStoreUrl);
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Authorization", "Bearer " + mcAccessToken);
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setUseCaches(false);
|
||||
conn.connect();
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
|
||||
JSONArray ja = jo.getJSONArray("items");
|
||||
Log.i("MicroAuth","Store Len = " + ja.length());
|
||||
for(int i = 0; i < ja.length(); i++) {
|
||||
String prod = ja.getJSONObject(i).getString("name");
|
||||
Log.i("MicroAuth","Product " + i +": " +prod);
|
||||
}
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMcProfile(String mcAccessToken) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(mcProfileUrl);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Authorization", "Bearer " + mcAccessToken);
|
||||
conn.setUseCaches(false);
|
||||
conn.connect();
|
||||
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
String s= Tools.read(conn.getInputStream());
|
||||
Log.i("MicroAuth","profile:" + s);
|
||||
JSONObject jsonObject = new JSONObject(s);
|
||||
String name = (String) jsonObject.get("name");
|
||||
String uuid = (String) jsonObject.get("id");
|
||||
String uuidDashes = uuid .replaceFirst(
|
||||
"(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"
|
||||
);
|
||||
doesOwnGame = true;
|
||||
Log.i("MicroAuth","UserName = " + name);
|
||||
Log.i("MicroAuth","Uuid Minecraft = " + uuidDashes);
|
||||
mcName=name;
|
||||
mcUuid=uuidDashes;
|
||||
}else{
|
||||
Log.i("MicroAuth","It seems that this Microsoft Account does not own the game.");
|
||||
doesOwnGame = false;
|
||||
throwResponseError(conn);
|
||||
}
|
||||
}
|
||||
|
||||
public static String ofJSONData(Map<Object, Object> data) {
|
||||
return new JSONObject(data).toString();
|
||||
}
|
||||
|
||||
public static String ofFormData(Map<Object, Object> data) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<Object, Object> entry : data.entrySet()) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append("&");
|
||||
}
|
||||
try {
|
||||
builder.append(URLEncoder.encode(entry.getKey().toString(), "UTF-8"));
|
||||
builder.append("=");
|
||||
builder.append(URLEncoder.encode(entry.getValue().toString(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
//Should not happen
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static void throwResponseError(HttpURLConnection conn) throws IOException {
|
||||
String otherErrStr = "";
|
||||
String errStr = Tools.read(conn.getErrorStream());
|
||||
Log.i("MicroAuth","Error code: " + conn.getResponseCode() + ": " + conn.getResponseMessage() + "\n" + errStr);
|
||||
|
||||
if (errStr.contains("NOT_FOUND") &&
|
||||
errStr.contains("The server has not found anything matching the request URI"))
|
||||
{
|
||||
// TODO localize this
|
||||
otherErrStr = "It seems that this Microsoft Account does not own the game. Make sure that you have bought/migrated to your Microsoft account.";
|
||||
}
|
||||
|
||||
throw new RuntimeException(otherErrStr + "\n\nMSA Error: " + conn.getResponseCode() + ": " + conn.getResponseMessage() + ", error stream:\n" + errStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.microsoft.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
public class MicrosoftLoginGUIActivity extends AppCompatActivity {
|
||||
public static final int AUTHENTICATE_MICROSOFT_REQUEST = 60;
|
||||
WebView webView;
|
||||
ProgressDialog waitDialog;
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
waitDialog = new ProgressDialog(this);
|
||||
waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
waitDialog.setIndeterminate(true);
|
||||
waitDialog.setMessage(getString(R.string.global_waiting));
|
||||
webView = new WebView(this);
|
||||
webView.setWebViewClient(new WebViewTrackClient());
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
webView.loadUrl("https://login.live.com/oauth20_authorize.srf" +
|
||||
"?client_id=00000000402b5328" +
|
||||
"&response_type=code" +
|
||||
"&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL" +
|
||||
"&redirect_url=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf");
|
||||
setContentView(webView);
|
||||
}
|
||||
class WebViewTrackClient extends WebViewClient {
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if(url.startsWith("ms-xal-00000000402b5328")) {
|
||||
Intent data = new Intent();
|
||||
data.setData(Uri.parse(url));
|
||||
if(waitDialog.isShowing()) waitDialog.dismiss();
|
||||
setResult(Activity.RESULT_OK,data);
|
||||
finish();
|
||||
return true;
|
||||
}else{
|
||||
return super.shouldOverrideUrlLoading(view, url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
//super.onPageStarted(view, url, favicon);
|
||||
if(!waitDialog.isShowing()) waitDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
waitDialog.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.*;
|
||||
import java.io.*;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
public class InvalidateTokenTask extends AsyncTask<String, Void, Throwable> {
|
||||
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
|
||||
//private Gson gson = new Gson();
|
||||
private MinecraftAccount profilePath;
|
||||
|
||||
private final WeakReference<Context> ctx;
|
||||
private String path;
|
||||
|
||||
public InvalidateTokenTask(Context ctx) {
|
||||
this.ctx = new WeakReference<>(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable doInBackground(String... args) {
|
||||
path = args[0];
|
||||
try {
|
||||
|
||||
|
||||
this.profilePath = MinecraftAccount.load(args[0]);
|
||||
if (profilePath.accessToken.equals("0")) {
|
||||
return null;
|
||||
}
|
||||
this.authenticator.invalidate(profilePath.accessToken,
|
||||
UUID.fromString(profilePath.isMicrosoft ? profilePath.profileId : profilePath.clientToken /* should be? */));
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Throwable result) {
|
||||
if (result != null) {
|
||||
Tools.showError(ctx.get(), result);
|
||||
}
|
||||
new File(Tools.DIR_ACCOUNT_NEW + "/" + path + ".json").delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
public interface LoginListener
|
||||
{
|
||||
public void onBeforeLogin();
|
||||
public void onLoginDone(String[] result);
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
import android.os.*;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class LoginTask extends AsyncTask<String, Void, Void>
|
||||
{
|
||||
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
|
||||
//private String TAG = "MojangAuth-login";
|
||||
private LoginListener listener;
|
||||
|
||||
public LoginTask setLoginListener(LoginListener listener) {
|
||||
this.listener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
private UUID getRandomUUID() {
|
||||
return UUID.randomUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
listener.onBeforeLogin();
|
||||
|
||||
super.onPreExecute();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(String[] args) {
|
||||
ArrayList<String> str = new ArrayList<String>();
|
||||
str.add("ERROR");
|
||||
try{
|
||||
try{
|
||||
AuthenticateResponse response = authenticator.authenticate(args[0], args[1], getRandomUUID());
|
||||
if (response.selectedProfile == null) {
|
||||
str.add("Can't login a demo account!\n");
|
||||
} else {
|
||||
if (new File(Tools.DIR_ACCOUNT_NEW + "/" + response.selectedProfile.name + ".json").exists()) {
|
||||
str.add("This account already exist!\n");
|
||||
} else {
|
||||
str.add(response.accessToken); // Access token
|
||||
str.add(response.clientToken.toString()); // Client token
|
||||
str.add(response.selectedProfile.id); // Profile ID
|
||||
str.add(response.selectedProfile.name); // Username
|
||||
str.set(0, "NORMAL");
|
||||
}
|
||||
}
|
||||
}
|
||||
//MainActivity.updateStatus(804);
|
||||
catch(Throwable e){
|
||||
str.add(e.getMessage());
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
str.add(e.getMessage());
|
||||
}
|
||||
|
||||
listener.onLoginDone(str.toArray(new String[0]));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
// listener.onLoginDone(result);
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
public interface RefreshListener
|
||||
{
|
||||
public void onFailed(Throwable e);
|
||||
public void onSuccess(MinecraftAccount profile);
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.*;
|
||||
import android.app.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
public class RefreshTokenTask extends AsyncTask<String, Void, Throwable> {
|
||||
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
|
||||
//private Gson gson = new Gson();
|
||||
private RefreshListener listener;
|
||||
private MinecraftAccount profilePath;
|
||||
|
||||
private final WeakReference<Context> ctx;
|
||||
private ProgressDialog build;
|
||||
|
||||
public RefreshTokenTask(Context ctx, RefreshListener listener) {
|
||||
this.ctx = new WeakReference<>(ctx);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreExecute() {
|
||||
build = new ProgressDialog(ctx.get());
|
||||
build.setMessage(ctx.get().getString(R.string.global_waiting));
|
||||
build.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
build.setCancelable(false);
|
||||
build.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable doInBackground(String... args) {
|
||||
try {
|
||||
this.profilePath = MinecraftAccount.load(args[0]);
|
||||
int responseCode = 400;
|
||||
try {
|
||||
responseCode = this.authenticator.validate(profilePath.accessToken).statusCode;
|
||||
}catch(RuntimeException e) {}
|
||||
|
||||
if (responseCode == 403) {
|
||||
RefreshResponse response = this.authenticator.refresh(profilePath.accessToken, UUID.fromString(profilePath.clientToken));
|
||||
if (response == null) {
|
||||
// Refresh when offline?
|
||||
return null;
|
||||
} else if (response.selectedProfile == null) {
|
||||
throw new IllegalArgumentException("Can't refresh a demo account!");
|
||||
}
|
||||
|
||||
profilePath.clientToken = response.clientToken.toString();
|
||||
profilePath.accessToken = response.accessToken;
|
||||
profilePath.username = response.selectedProfile.name;
|
||||
profilePath.profileId = response.selectedProfile.id;
|
||||
}
|
||||
profilePath.updateSkinFace();
|
||||
profilePath.save();
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Throwable result) {
|
||||
build.dismiss();
|
||||
if (result == null) {
|
||||
listener.onSuccess(profilePath);
|
||||
} else {
|
||||
listener.onFailed(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthenticateRequest {
|
||||
public AgentInfo agent = new AgentInfo();
|
||||
public UUID clientToken;
|
||||
public String password;
|
||||
public String username;
|
||||
|
||||
public static class AgentInfo {
|
||||
public String name;
|
||||
public int version;
|
||||
}
|
||||
|
||||
public AuthenticateRequest(String username, String password, UUID clientToken, String clientName, int clientVersion) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.clientToken = clientToken;
|
||||
this.agent.name = clientName;
|
||||
this.agent.version = clientVersion;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthenticateResponse {
|
||||
public String accessToken;
|
||||
public Profile[] availableProfiles;
|
||||
public UUID clientToken;
|
||||
public Profile selectedProfile;
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
public class ErrorResponse {
|
||||
public String cause;
|
||||
public String error;
|
||||
public String errorMessage;
|
||||
}
|
||||
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
public class NetworkResponse
|
||||
{
|
||||
public final int statusCode;
|
||||
public final Object response;
|
||||
public NetworkResponse(int statusCode, Object response) {
|
||||
this.statusCode = statusCode;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public void throwExceptionIfNeed(String msg) {
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void throwExceptionIfNeed() {
|
||||
throwExceptionIfNeed("Respone error, code: " + statusCode + ", message: " + response);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
public class Profile {
|
||||
public String id;
|
||||
public boolean legacy;
|
||||
public String name;
|
||||
}
|
||||
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class RefreshRequest {
|
||||
public String accessToken;
|
||||
public UUID clientToken;
|
||||
|
||||
public RefreshRequest(String accessToken, UUID clientToken) {
|
||||
this.accessToken = accessToken;
|
||||
this.clientToken = clientToken;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class RefreshResponse {
|
||||
public String accessToken;
|
||||
public UUID clientToken;
|
||||
public Profile selectedProfile;
|
||||
}
|
||||
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import android.util.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import org.apache.commons.io.*;
|
||||
|
||||
public class YggdrasilAuthenticator {
|
||||
private static final String API_URL = "https://authserver.mojang.com/";
|
||||
private String clientName = "Minecraft";
|
||||
private int clientVersion = 1;
|
||||
|
||||
private NetworkResponse makeRequest(String endpoint, Object inputObject, Class<?> responseClass) throws IOException, Throwable {
|
||||
Throwable th;
|
||||
InputStream is = null;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
String requestJson = Tools.GLOBAL_GSON.toJson(inputObject);
|
||||
try {
|
||||
URL url = new URL(API_URL + endpoint);
|
||||
OutputStream os;
|
||||
try {
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestProperty("User-Agent", "Minecraft");
|
||||
conn.setDoInput(true);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.connect();
|
||||
os = null;
|
||||
os = conn.getOutputStream();
|
||||
os.write(requestJson.getBytes(Charset.forName("UTF-8")));
|
||||
if (os != null) {
|
||||
os.close();
|
||||
}
|
||||
|
||||
int statusCode = conn.getResponseCode();
|
||||
if (statusCode != 200) {
|
||||
is = conn.getErrorStream();
|
||||
} else {
|
||||
is = conn.getInputStream();
|
||||
}
|
||||
|
||||
if (is != null) {
|
||||
IOUtils.copy(is, bos);
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
String outString = new String(bos.toByteArray(), Charset.forName("UTF-8"));
|
||||
if (statusCode == 200 || statusCode == 204){
|
||||
Log.i("Result", "Task " + endpoint + " successful");
|
||||
|
||||
if (responseClass != null) {
|
||||
return new NetworkResponse(statusCode, Tools.GLOBAL_GSON.fromJson(outString, responseClass));
|
||||
}
|
||||
} else {
|
||||
Log.i("Result", "Task " + endpoint + " failure");
|
||||
}
|
||||
|
||||
return new NetworkResponse(statusCode, outString);
|
||||
} catch (UnknownHostException e) {
|
||||
if (endpoint.equals("refresh")) {
|
||||
return null;
|
||||
} else {
|
||||
throw new RuntimeException("Can't connect to the server", e);
|
||||
}
|
||||
} catch (Throwable th2) {
|
||||
th = th2;
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception e2) {
|
||||
e2.addSuppressed(th2);
|
||||
throw e2;
|
||||
}
|
||||
}
|
||||
throw th;
|
||||
}
|
||||
} catch (Throwable th3) {
|
||||
th = th3;
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
throw th;
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticateResponse authenticate(String username, String password, UUID clientId) throws IOException, Throwable {
|
||||
NetworkResponse obj = makeRequest("authenticate", new AuthenticateRequest(username, password, clientId, this.clientName, this.clientVersion), AuthenticateResponse.class);
|
||||
/*
|
||||
if (obj.statusCode != 200) {
|
||||
throw new RuntimeException("Invalid username or password, status code: " + obj.statusCode);
|
||||
}
|
||||
*/
|
||||
obj.throwExceptionIfNeed();
|
||||
return (AuthenticateResponse) obj.response;
|
||||
}
|
||||
|
||||
public RefreshResponse refresh(String authToken, UUID clientId) throws IOException, Throwable {
|
||||
NetworkResponse obj = makeRequest("refresh", new RefreshRequest(authToken, clientId), RefreshResponse.class);
|
||||
if (obj == null) {
|
||||
return null;
|
||||
} else {
|
||||
obj.throwExceptionIfNeed(); // "Invalid username or password, status code: " + obj.statusCode);
|
||||
return (RefreshResponse) obj.response;
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkResponse validate(String authToken) throws Throwable {
|
||||
return makeRequest("validate", new RefreshRequest(authToken, null), null);
|
||||
}
|
||||
|
||||
public NetworkResponse invalidate(String authToken, UUID clientId) throws Throwable {
|
||||
return makeRequest("invalidate", new RefreshRequest(authToken, clientId), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,313 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
|
||||
import android.util.*;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
import net.objecthunter.exp4j.*;
|
||||
import net.objecthunter.exp4j.function.Function;
|
||||
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
import static net.kdt.pojavlaunch.LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN;
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public class ControlData {
|
||||
|
||||
public static final int SPECIALBTN_KEYBOARD = -1;
|
||||
public static final int SPECIALBTN_TOGGLECTRL = -2;
|
||||
public static final int SPECIALBTN_MOUSEPRI = -3;
|
||||
public static final int SPECIALBTN_MOUSESEC = -4;
|
||||
public static final int SPECIALBTN_VIRTUALMOUSE = -5;
|
||||
public static final int SPECIALBTN_MOUSEMID = -6;
|
||||
public static final int SPECIALBTN_SCROLLUP = -7;
|
||||
public static final int SPECIALBTN_SCROLLDOWN = -8;
|
||||
|
||||
private static ControlData[] SPECIAL_BUTTONS;
|
||||
private static String[] SPECIAL_BUTTON_NAME_ARRAY;
|
||||
|
||||
// Internal usage only
|
||||
public boolean isHideable;
|
||||
|
||||
private static WeakReference<ExpressionBuilder> builder = new WeakReference<>(null);
|
||||
private static WeakReference<Field> expression = new WeakReference<>(null);
|
||||
private static WeakReference<ArrayMap<String , String>> conversionMap = new WeakReference<>(null);
|
||||
static {
|
||||
bypassExpressionBuilder();
|
||||
buildConversionMap();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Both fields below are dynamic position data, auto updates
|
||||
* X and Y position, unlike the original one which uses fixed
|
||||
* position, so it does not provide auto-location when a control
|
||||
* is made on a small device, then import the control to a
|
||||
* bigger device or vice versa.
|
||||
*/
|
||||
public String dynamicX, dynamicY;
|
||||
public boolean isDynamicBtn, isToggle, passThruEnabled;
|
||||
|
||||
public static ControlData[] getSpecialButtons(){
|
||||
if (SPECIAL_BUTTONS == null) {
|
||||
SPECIAL_BUTTONS = new ControlData[]{
|
||||
new ControlData("Keyboard", new int[]{SPECIALBTN_KEYBOARD}, "${margin} * 3 + ${width} * 2", "${margin}", false),
|
||||
new ControlData("GUI", new int[]{SPECIALBTN_TOGGLECTRL}, "${margin}", "${bottom} - ${margin}"),
|
||||
new ControlData("PRI", new int[]{SPECIALBTN_MOUSEPRI}, "${margin}", "${screen_height} - ${margin} * 3 - ${height} * 3"),
|
||||
new ControlData("SEC", new int[]{SPECIALBTN_MOUSESEC}, "${margin} * 3 + ${width} * 2", "${screen_height} - ${margin} * 3 - ${height} * 3"),
|
||||
new ControlData("Mouse", new int[]{SPECIALBTN_VIRTUALMOUSE}, "${right}", "${margin}", false),
|
||||
|
||||
new ControlData("MID", new int[]{SPECIALBTN_MOUSEMID}, "${margin}", "${margin}"),
|
||||
new ControlData("SCROLLUP", new int[]{SPECIALBTN_SCROLLUP}, "${margin}", "${margin}"),
|
||||
new ControlData("SCROLLDOWN", new int[]{SPECIALBTN_SCROLLDOWN}, "${margin}", "${margin}")
|
||||
};
|
||||
}
|
||||
|
||||
return SPECIAL_BUTTONS;
|
||||
}
|
||||
|
||||
public static String[] buildSpecialButtonArray() {
|
||||
if (SPECIAL_BUTTON_NAME_ARRAY == null) {
|
||||
List<String> nameList = new ArrayList<String>();
|
||||
for (ControlData btn : getSpecialButtons()) {
|
||||
nameList.add(btn.name);
|
||||
}
|
||||
SPECIAL_BUTTON_NAME_ARRAY = nameList.toArray(new String[0]);
|
||||
}
|
||||
|
||||
return SPECIAL_BUTTON_NAME_ARRAY;
|
||||
}
|
||||
|
||||
public String name;
|
||||
private float width; //Dp instead of Px now
|
||||
private float height; //Dp instead of Px now
|
||||
public int[] keycodes; //Should store up to 4 keys
|
||||
public float opacity; //Alpha value from 0 to 1;
|
||||
public int bgColor;
|
||||
public int strokeColor;
|
||||
public int strokeWidth; //0-100%
|
||||
public float cornerRadius; //0-100%
|
||||
public boolean isSwipeable;
|
||||
|
||||
public ControlData() {
|
||||
this("button");
|
||||
}
|
||||
|
||||
public ControlData(String name){
|
||||
this(name, new int[] {});
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes) {
|
||||
this(name, keycodes, Tools.currentDisplayMetrics.widthPixels/2, Tools.currentDisplayMetrics.heightPixels/2);
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes, float x, float y) {
|
||||
this(name, keycodes, x, y, 50, 50);
|
||||
}
|
||||
|
||||
public ControlData(android.content.Context ctx, int resId, int[] keycodes, float x, float y, boolean isSquare) {
|
||||
this(ctx.getResources().getString(resId), keycodes, x, y, isSquare);
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes, float x, float y, boolean isSquare) {
|
||||
this(name, keycodes, x, y, isSquare ? 50 : 80, isSquare ? 50 : 30);
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes, float x, float y, float width, float height) {
|
||||
this(name, keycodes, Float.toString(x), Float.toString(y), width, height, false);
|
||||
this.isDynamicBtn = false;
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY) {
|
||||
this(name, keycodes, dynamicX, dynamicY, 50, 50, false);
|
||||
}
|
||||
|
||||
public ControlData(android.content.Context ctx, int resId, int[] keycodes, String dynamicX, String dynamicY, boolean isSquare) {
|
||||
this(ctx.getResources().getString(resId), keycodes, dynamicX, dynamicY, isSquare);
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, boolean isSquare) {
|
||||
this(name, keycodes, dynamicX, dynamicY, isSquare ? 50 : 80, isSquare ? 50 : 30, false);
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, float width, float height, boolean isToggle){
|
||||
this(name, keycodes, dynamicX, dynamicY, width, height, isToggle, 1,0x4D000000, 0xFFFFFFFF,0,0);
|
||||
}
|
||||
|
||||
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, float width, float height, boolean isToggle, float opacity, int bgColor, int strokeColor, int strokeWidth, float cornerRadius) {
|
||||
this.name = name;
|
||||
this.keycodes = inflateKeycodeArray(keycodes);
|
||||
this.dynamicX = dynamicX;
|
||||
this.dynamicY = dynamicY;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.isDynamicBtn = false;
|
||||
this.isToggle = isToggle;
|
||||
this.opacity = opacity;
|
||||
this.bgColor = bgColor;
|
||||
this.strokeColor = strokeColor;
|
||||
this.strokeWidth = strokeWidth;
|
||||
this.cornerRadius = cornerRadius;
|
||||
}
|
||||
|
||||
//Deep copy constructor
|
||||
public ControlData(ControlData controlData){
|
||||
this(
|
||||
controlData.name,
|
||||
controlData.keycodes,
|
||||
controlData.dynamicX,
|
||||
controlData.dynamicY,
|
||||
controlData.width,
|
||||
controlData.height,
|
||||
controlData.isToggle,
|
||||
controlData.opacity,
|
||||
controlData.bgColor,
|
||||
controlData.strokeColor,
|
||||
controlData.strokeWidth,
|
||||
controlData.cornerRadius
|
||||
);
|
||||
}
|
||||
|
||||
public void execute(boolean isDown) {
|
||||
for(int keycode : keycodes){
|
||||
sendKeyPress(keycode, 0, isDown);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public float insertDynamicPos(String dynamicPos) {
|
||||
// Insert value to ${variable}
|
||||
String insertedPos = JSONUtils.insertSingleJSONValue(dynamicPos, fillConversionMap());
|
||||
|
||||
// Calculate, because the dynamic position contains some math equations
|
||||
return calculate(insertedPos);
|
||||
}
|
||||
|
||||
private static float calculate(String math) {
|
||||
setExpression(math);
|
||||
return (float) builder.get().build().evaluate();
|
||||
}
|
||||
|
||||
private static int[] inflateKeycodeArray(int[] keycodes){
|
||||
int[] inflatedArray = new int[]{GLFW_KEY_UNKNOWN, GLFW_KEY_UNKNOWN, GLFW_KEY_UNKNOWN, GLFW_KEY_UNKNOWN};
|
||||
System.arraycopy(keycodes, 0, inflatedArray, 0, keycodes.length);
|
||||
return inflatedArray;
|
||||
}
|
||||
|
||||
|
||||
public boolean containsKeycode(int keycodeToCheck){
|
||||
for(int keycode : keycodes)
|
||||
if(keycodeToCheck == keycode)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Getters || setters (with conversion for ease of use)
|
||||
public float getWidth() {
|
||||
return Tools.dpToPx(width);
|
||||
}
|
||||
|
||||
public float getHeight(){
|
||||
return Tools.dpToPx(height);
|
||||
}
|
||||
|
||||
|
||||
public void setWidth(float widthInPx){
|
||||
width = Tools.pxToDp(widthInPx);
|
||||
}
|
||||
|
||||
public void setHeight(float heightInPx){
|
||||
height = Tools.pxToDp(heightInPx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a weak reference to a builder and its expression field.
|
||||
* Although VERY bad practice it isn't slower due to saved GC time.
|
||||
* The normal way requires us to create ONE builder and TWO functions for EACH button.
|
||||
*/
|
||||
private static void bypassExpressionBuilder(){
|
||||
ExpressionBuilder expressionBuilder = new ExpressionBuilder("1 + 1")
|
||||
.function(new Function("dp", 1) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return Tools.pxToDp((float) args[0]);
|
||||
}
|
||||
})
|
||||
.function(new Function("px", 1) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return Tools.dpToPx((float) args[0]);
|
||||
}
|
||||
});
|
||||
builder = new WeakReference<>(expressionBuilder);
|
||||
try {
|
||||
expression = new WeakReference<>(builder.get().getClass().getDeclaredField("expression"));
|
||||
expression.get().setAccessible(true);
|
||||
expression.get().set(expression.get(), expression.get().getModifiers() & ~Modifier.FINAL);
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper for the WeakReference to the expressionField.
|
||||
* @param stringExpression the expression to set.
|
||||
*/
|
||||
private static void setExpression(String stringExpression){
|
||||
if(builder.get() == null) bypassExpressionBuilder();
|
||||
try {
|
||||
expression.get().set(builder.get(), stringExpression);
|
||||
}catch (IllegalAccessException e){}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a shared conversion map without the ControlData dependent values
|
||||
* You need to set the view dependent values before using it.
|
||||
*/
|
||||
private static void buildConversionMap() {
|
||||
// Values in the map below may be always changed
|
||||
ArrayMap<String, String> keyValueMap = new ArrayMap<>(10);
|
||||
keyValueMap.put("top", "0");
|
||||
keyValueMap.put("left", "0");
|
||||
keyValueMap.put("right", "DUMMY_RIGHT");
|
||||
keyValueMap.put("bottom", "DUMMY_BOTTOM");
|
||||
keyValueMap.put("width", "DUMMY_WIDTH");
|
||||
keyValueMap.put("height", "DUMMY_HEIGHT");
|
||||
keyValueMap.put("screen_width", Integer.toString(CallbackBridge.physicalWidth));
|
||||
keyValueMap.put("screen_height", Integer.toString(CallbackBridge.physicalHeight));
|
||||
keyValueMap.put("margin", Integer.toString((int) Tools.dpToPx(2)));
|
||||
keyValueMap.put("preferred_scale", Float.toString(LauncherPreferences.PREF_BUTTONSIZE));
|
||||
|
||||
conversionMap = new WeakReference<>(keyValueMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the conversionMap with controlData dependent values.
|
||||
* The returned valueMap should NOT be kept in memory.
|
||||
* @return the valueMap to use.
|
||||
*/
|
||||
private Map<String, String> fillConversionMap(){
|
||||
ArrayMap<String, String> valueMap = conversionMap.get();
|
||||
if (valueMap == null){
|
||||
buildConversionMap();
|
||||
valueMap = conversionMap.get();
|
||||
}
|
||||
|
||||
valueMap.put("right", Float.toString(CallbackBridge.physicalWidth - getWidth()));
|
||||
valueMap.put("bottom", Float.toString(CallbackBridge.physicalHeight - getHeight()));
|
||||
valueMap.put("width", Float.toString(getWidth()));
|
||||
valueMap.put("height", Float.toString(getHeight()));
|
||||
|
||||
return valueMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.DOWN;
|
||||
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.LEFT;
|
||||
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.RIGHT;
|
||||
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.UP;
|
||||
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.FREE;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public class ControlDrawerData {
|
||||
|
||||
public ArrayList<ControlData> buttonProperties;
|
||||
public ControlData properties;
|
||||
public Orientation orientation;
|
||||
|
||||
@androidx.annotation.Keep
|
||||
public enum Orientation {
|
||||
DOWN,
|
||||
LEFT,
|
||||
UP,
|
||||
RIGHT,
|
||||
FREE
|
||||
}
|
||||
|
||||
public static Orientation[] getOrientations(){
|
||||
return new Orientation[]{DOWN,LEFT,UP,RIGHT,FREE};
|
||||
}
|
||||
|
||||
public static int orientationToInt(Orientation orientation){
|
||||
switch (orientation){
|
||||
case DOWN: return 0;
|
||||
case LEFT: return 1;
|
||||
case UP: return 2;
|
||||
case RIGHT: return 3;
|
||||
case FREE: return 4;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static Orientation intToOrientation(int by){
|
||||
switch (by){
|
||||
case 0: return DOWN;
|
||||
case 1: return LEFT;
|
||||
case 2: return UP;
|
||||
case 3: return RIGHT;
|
||||
case 4: return FREE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ControlDrawerData(){
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
public ControlDrawerData(ArrayList<ControlData> buttonProperties){
|
||||
this(buttonProperties, new ControlData("Drawer", new int[] {}, Tools.currentDisplayMetrics.widthPixels/2, Tools.currentDisplayMetrics.heightPixels/2));
|
||||
}
|
||||
|
||||
public ControlDrawerData(ArrayList<ControlData> buttonProperties, ControlData properties){
|
||||
this(buttonProperties, properties, Orientation.LEFT);
|
||||
}
|
||||
|
||||
|
||||
public ControlDrawerData(ArrayList<ControlData> buttonProperties, ControlData properties, Orientation orientation){
|
||||
this.buttonProperties = buttonProperties;
|
||||
this.properties = properties;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public ControlDrawerData(ControlDrawerData drawerData){
|
||||
buttonProperties = new ArrayList<>(drawerData.buttonProperties.size());
|
||||
for(ControlData controlData : drawerData.buttonProperties){
|
||||
buttonProperties.add(new ControlData(controlData));
|
||||
}
|
||||
properties = new ControlData(drawerData.properties);
|
||||
orientation = drawerData.orientation;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,299 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
import android.content.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import com.google.gson.*;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
|
||||
import net.kdt.pojavlaunch.customcontrols.handleview.HandleView;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
|
||||
public class ControlLayout extends FrameLayout
|
||||
{
|
||||
protected CustomControls mLayout;
|
||||
private boolean mModifiable;
|
||||
private CustomControlsActivity mActivity;
|
||||
private boolean mControlVisible = false;
|
||||
|
||||
public ControlLayout(Context ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
public ControlLayout(Context ctx, AttributeSet attrs) {
|
||||
super(ctx, attrs);
|
||||
}
|
||||
|
||||
public void hideAllHandleViews() {
|
||||
for(ControlButton button : getButtonChildren()){
|
||||
HandleView hv = button.getHandleView();
|
||||
if(hv != null) hv.hide();
|
||||
}
|
||||
}
|
||||
|
||||
public void loadLayout(String jsonPath) throws IOException, JsonSyntaxException {
|
||||
CustomControls layout = LayoutConverter.loadAndConvertIfNecessary(jsonPath);
|
||||
if(layout != null) {
|
||||
loadLayout(layout);
|
||||
}else{
|
||||
throw new IOException("Unsupported control layout version");
|
||||
}
|
||||
}
|
||||
|
||||
public void loadLayout(CustomControls controlLayout) {
|
||||
if (mModifiable)
|
||||
hideAllHandleViews();
|
||||
|
||||
removeAllButtons();
|
||||
if(mLayout != null) {
|
||||
mLayout.mControlDataList = null;
|
||||
mLayout = null;
|
||||
}
|
||||
|
||||
System.gc();
|
||||
mapTable.clear();
|
||||
|
||||
// Cleanup buttons only when input layout is null
|
||||
if (controlLayout == null) return;
|
||||
|
||||
mLayout = controlLayout;
|
||||
|
||||
//CONTROL BUTTON
|
||||
for (ControlData button : controlLayout.mControlDataList) {
|
||||
addControlView(button);
|
||||
}
|
||||
|
||||
//CONTROL DRAWER
|
||||
for(ControlDrawerData drawerData : controlLayout.mDrawerDataList){
|
||||
ControlDrawer drawer = addDrawerView(drawerData);
|
||||
if(mModifiable) drawer.areButtonsVisible = true;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
mLayout.scaledAt = LauncherPreferences.PREF_BUTTONSIZE;
|
||||
|
||||
setModified(false);
|
||||
} // loadLayout
|
||||
|
||||
//CONTROL BUTTON
|
||||
public void addControlButton(ControlData controlButton) {
|
||||
mLayout.mControlDataList.add(controlButton);
|
||||
addControlView(controlButton);
|
||||
}
|
||||
|
||||
private void addControlView(ControlData controlButton) {
|
||||
final ControlButton view = new ControlButton(this, controlButton);
|
||||
view.setModifiable(mModifiable);
|
||||
if (!mModifiable) {
|
||||
view.setAlpha(view.getProperties().opacity);
|
||||
view.setFocusable(false);
|
||||
view.setFocusableInTouchMode(false);
|
||||
}
|
||||
addView(view);
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
// CONTROL DRAWER
|
||||
public void addDrawer(ControlDrawerData drawerData){
|
||||
mLayout.mDrawerDataList.add(drawerData);
|
||||
addDrawerView();
|
||||
}
|
||||
|
||||
private void addDrawerView(){
|
||||
addDrawerView(null);
|
||||
}
|
||||
|
||||
private ControlDrawer addDrawerView(ControlDrawerData drawerData){
|
||||
|
||||
final ControlDrawer view = new ControlDrawer(this,drawerData == null ? mLayout.mDrawerDataList.get(mLayout.mDrawerDataList.size()-1) : drawerData);
|
||||
view.setModifiable(mModifiable);
|
||||
if (!mModifiable) {
|
||||
view.setAlpha(view.getProperties().opacity);
|
||||
view.setFocusable(false);
|
||||
view.setFocusableInTouchMode(false);
|
||||
}
|
||||
addView(view);
|
||||
//CONTROL SUB BUTTON
|
||||
for (ControlData subButton : view.getDrawerData().buttonProperties) {
|
||||
addSubView(view, subButton);
|
||||
}
|
||||
|
||||
setModified(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
//CONTROL SUB-BUTTON
|
||||
public void addSubButton(ControlDrawer drawer, ControlData controlButton){
|
||||
//Yep there isn't much here
|
||||
drawer.getDrawerData().buttonProperties.add(controlButton);
|
||||
addSubView(drawer, drawer.getDrawerData().buttonProperties.get(drawer.getDrawerData().buttonProperties.size()-1 ));
|
||||
}
|
||||
|
||||
public void addSubView(ControlDrawer drawer, ControlData controlButton){
|
||||
final ControlSubButton view = new ControlSubButton(this, controlButton, drawer);
|
||||
view.setModifiable(mModifiable);
|
||||
if (!mModifiable) {
|
||||
view.setAlpha(view.getProperties().opacity);
|
||||
view.setFocusable(false);
|
||||
view.setFocusableInTouchMode(false);
|
||||
}else{
|
||||
view.setVisible(drawer.areButtonsVisible);
|
||||
}
|
||||
|
||||
drawer.addButton(view);
|
||||
addView(view);
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
private void removeAllButtons() {
|
||||
for(View v : getButtonChildren()){
|
||||
removeView(v);
|
||||
}
|
||||
|
||||
System.gc();
|
||||
//i wanna be sure that all the removed Views will be removed after a reload
|
||||
//because if frames will slowly go down after many control changes it will be warm and bad
|
||||
}
|
||||
|
||||
public void removeControlButton(ControlButton controlButton) {
|
||||
mLayout.mControlDataList.remove(controlButton.getProperties());
|
||||
controlButton.setVisibility(View.GONE);
|
||||
removeView(controlButton);
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
public void removeControlDrawer(ControlDrawer controlDrawer){
|
||||
for(ControlSubButton subButton : controlDrawer.buttons){
|
||||
subButton.setVisibility(GONE);
|
||||
removeView(subButton);
|
||||
}
|
||||
mLayout.mDrawerDataList.remove(controlDrawer.getDrawerData());
|
||||
controlDrawer.setVisibility(GONE);
|
||||
removeView(controlDrawer);
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
public void removeControlSubButton(ControlSubButton subButton){
|
||||
subButton.parentDrawer.drawerData.buttonProperties.remove(subButton.getProperties());
|
||||
subButton.parentDrawer.buttons.remove(subButton);
|
||||
|
||||
subButton.parentDrawer.syncButtons();
|
||||
|
||||
subButton.setVisibility(GONE);
|
||||
removeView(subButton);
|
||||
|
||||
}
|
||||
|
||||
public void saveLayout(String path) throws Exception {
|
||||
mLayout.save(path);
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
public void setActivity(CustomControlsActivity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
public void toggleControlVisible(){
|
||||
mControlVisible = !mControlVisible;
|
||||
setControlVisible(mControlVisible);
|
||||
}
|
||||
|
||||
public float getLayoutScale(){
|
||||
return mLayout.scaledAt;
|
||||
}
|
||||
|
||||
public void setControlVisible(boolean isVisible) {
|
||||
if (mModifiable) return; // Not using on custom controls activity
|
||||
|
||||
mControlVisible = isVisible;
|
||||
for(ControlButton button : getButtonChildren()){
|
||||
button.setVisible(isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
public void setModifiable(boolean isModifiable) {
|
||||
mModifiable = isModifiable;
|
||||
for(ControlButton button : getButtonChildren()){
|
||||
button.setModifiable(isModifiable);
|
||||
if (!isModifiable)
|
||||
button.setAlpha(button.getProperties().opacity);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getModifiable(){
|
||||
return mModifiable;
|
||||
}
|
||||
|
||||
public void setModified(boolean isModified) {
|
||||
if (mActivity != null) mActivity.isModified = isModified;
|
||||
|
||||
}
|
||||
|
||||
public ArrayList<ControlButton> getButtonChildren(){
|
||||
ArrayList<ControlButton> children = new ArrayList<>();
|
||||
for(int i=0; i<getChildCount(); ++i){
|
||||
View v = getChildAt(i);
|
||||
if(v instanceof ControlButton)
|
||||
children.add(((ControlButton) v));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
HashMap<View, ControlButton> mapTable = new HashMap<>();
|
||||
//While this is called onTouch, this should only be called from a ControlButton.
|
||||
public boolean onTouch(View v, MotionEvent ev) {
|
||||
ControlButton lastControlButton = mapTable.get(v);
|
||||
|
||||
//Check if the action is cancelling, reset the lastControl button associated to the view
|
||||
if(ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_CANCEL){
|
||||
if(lastControlButton != null) lastControlButton.sendKeyPresses(false);
|
||||
mapTable.put(v, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(ev.getActionMasked() != MotionEvent.ACTION_MOVE) return false;
|
||||
|
||||
//Optimization pass to avoid looking at all children again
|
||||
if(lastControlButton != null){
|
||||
if( ev.getRawX() > lastControlButton.getX() && ev.getRawX() < lastControlButton.getX() + lastControlButton.getWidth() &&
|
||||
ev.getRawY() > lastControlButton.getY() && ev.getRawY() < lastControlButton.getY() + lastControlButton.getHeight()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Release last keys
|
||||
if (lastControlButton != null) lastControlButton.sendKeyPresses(false);
|
||||
mapTable.put(v, null);
|
||||
|
||||
//Look for another SWIPEABLE button
|
||||
for(ControlButton button : getButtonChildren()){
|
||||
if(!button.getProperties().isSwipeable) continue;
|
||||
|
||||
if( ev.getRawX() > button.getX() && ev.getRawX() < button.getX() + button.getWidth() &&
|
||||
ev.getRawY() > button.getY() && ev.getRawY() < button.getY() + button.getHeight()){
|
||||
|
||||
//Press the new key
|
||||
if(!button.equals(lastControlButton)){
|
||||
button.sendKeyPresses(true);
|
||||
|
||||
mapTable.put(v, button);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
import android.content.*;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
@Keep
|
||||
public class CustomControls {
|
||||
public int version = -1;
|
||||
public float scaledAt;
|
||||
public List<ControlData> mControlDataList;
|
||||
public List<ControlDrawerData> mDrawerDataList;
|
||||
public CustomControls() {
|
||||
this(new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public CustomControls(List<ControlData> mControlDataList, List<ControlDrawerData> mDrawerDataList) {
|
||||
this.mControlDataList = mControlDataList;
|
||||
this.mDrawerDataList = mDrawerDataList;
|
||||
this.scaledAt = 100f;
|
||||
}
|
||||
|
||||
// Generate default control
|
||||
public CustomControls(Context ctx) {
|
||||
this();
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[0])); // Keyboard
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[1])); // GUI
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[2])); // Primary Mouse mControlDataList
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[3])); // Secondary Mouse mControlDataList
|
||||
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[4])); // Virtual mouse toggle
|
||||
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_debug, new int[]{LWJGLGLFWKeycode.GLFW_KEY_F3}, "${margin}", "${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_chat, new int[]{LWJGLGLFWKeycode.GLFW_KEY_T}, "${margin} * 2 + ${width}", "${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_listplayers, new int[]{LWJGLGLFWKeycode.GLFW_KEY_TAB}, "${margin} * 4 + ${width} * 3", "${margin}", false));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_thirdperson, new int[]{LWJGLGLFWKeycode.GLFW_KEY_F5}, "${margin}", "${height} + ${margin}", false));
|
||||
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_up, new int[]{LWJGLGLFWKeycode.GLFW_KEY_W}, "${margin} * 2 + ${width}", "${bottom} - ${margin} * 3 - ${height} * 2", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_left, new int[]{LWJGLGLFWKeycode.GLFW_KEY_A}, "${margin}", "${bottom} - ${margin} * 2 - ${height}", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_down, new int[]{LWJGLGLFWKeycode.GLFW_KEY_S}, "${margin} * 2 + ${width}", "${bottom} - ${margin}", true));
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_right, new int[]{LWJGLGLFWKeycode.GLFW_KEY_D}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin} * 2 - ${height}", true));
|
||||
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_inventory, new int[]{LWJGLGLFWKeycode.GLFW_KEY_E}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin}", true));
|
||||
|
||||
ControlData shiftData = new ControlData(ctx, R.string.control_shift, new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT}, "${margin} * 2 + ${width}", "${screen_height} - ${margin} * 2 - ${height} * 2", true);
|
||||
shiftData.isToggle = true;
|
||||
this.mControlDataList.add(shiftData);
|
||||
this.mControlDataList.add(new ControlData(ctx, R.string.control_jump, new int[]{LWJGLGLFWKeycode.GLFW_KEY_SPACE}, "${right} - ${margin} * 2 - ${width}", "${bottom} - ${margin} * 2 - ${height}", true));
|
||||
|
||||
//The default controls are conform to the V2
|
||||
version = 4;
|
||||
}
|
||||
|
||||
|
||||
public void save(String path) throws IOException {
|
||||
//Current version is the V2.4 so the version as to be marked as 4 !
|
||||
version = 4;
|
||||
|
||||
Tools.write(path, Tools.GLOBAL_GSON.toJson(this));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LayoutConverter {
|
||||
public static boolean convertLookType = false; //false = flat; true = classic
|
||||
public static CustomControls loadAndConvertIfNecessary(String jsonPath) throws IOException, JsonSyntaxException {
|
||||
String jsonLayoutData = Tools.read(jsonPath);
|
||||
try {
|
||||
JSONObject layoutJobj = new JSONObject(jsonLayoutData);
|
||||
|
||||
if(!layoutJobj.has("version")) { //v1 layout
|
||||
CustomControls layout = LayoutConverter.convertV1Layout(layoutJobj);
|
||||
layout.save(jsonPath);
|
||||
return layout;
|
||||
}else if (layoutJobj.getInt("version") == 2) {
|
||||
CustomControls layout = LayoutConverter.convertV2Layout(layoutJobj);
|
||||
layout.save(jsonPath);
|
||||
return layout;
|
||||
}else if (layoutJobj.getInt("version") == 3 || layoutJobj.getInt("version") == 4) {
|
||||
return Tools.GLOBAL_GSON.fromJson(jsonLayoutData, CustomControls.class);
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}catch (JSONException e) {
|
||||
throw new JsonSyntaxException("Failed to load",e);
|
||||
}
|
||||
}
|
||||
public static CustomControls convertV2Layout(JSONObject oldLayoutJson) throws JSONException {
|
||||
CustomControls layout = Tools.GLOBAL_GSON.fromJson(oldLayoutJson.toString(), CustomControls.class);
|
||||
JSONArray layoutMainArray = oldLayoutJson.getJSONArray("mControlDataList");
|
||||
layout.mControlDataList = new ArrayList<>(layoutMainArray.length());
|
||||
for(int i = 0; i < layoutMainArray.length(); i++) {
|
||||
JSONObject button = layoutMainArray.getJSONObject(i);
|
||||
ControlData n_button = Tools.GLOBAL_GSON.fromJson(button.toString(), ControlData.class);
|
||||
if((n_button.dynamicX == null || n_button.dynamicX.isEmpty())&&button.has("x")) {
|
||||
double buttonC = button.getDouble("x");
|
||||
double ratio = buttonC/CallbackBridge.physicalWidth;
|
||||
n_button.dynamicX = ratio + " * ${screen_width}";
|
||||
}
|
||||
if((n_button.dynamicY == null || n_button.dynamicY.isEmpty())&&button.has("y")) {
|
||||
double buttonC = button.getDouble("y");
|
||||
double ratio = buttonC/CallbackBridge.physicalHeight;
|
||||
n_button.dynamicY = ratio + " * ${screen_height}";
|
||||
}
|
||||
layout.mControlDataList.add(n_button);
|
||||
}
|
||||
JSONArray layoutDrawerArray = oldLayoutJson.getJSONArray("mDrawerDataList");
|
||||
layout.mDrawerDataList = new ArrayList<>();
|
||||
for(int i = 0; i < layoutDrawerArray.length(); i++) {
|
||||
JSONObject button = layoutDrawerArray.getJSONObject(i);
|
||||
JSONObject buttonProperties = button.getJSONObject("properties");
|
||||
ControlDrawerData n_button = Tools.GLOBAL_GSON.fromJson(button.toString(), ControlDrawerData.class);
|
||||
if((n_button.properties.dynamicX == null || n_button.properties.dynamicX.isEmpty())&&buttonProperties.has("x")) {
|
||||
double buttonC = buttonProperties.getDouble("x");
|
||||
double ratio = buttonC/CallbackBridge.physicalWidth;
|
||||
n_button.properties.dynamicX = ratio + " * ${screen_width}";
|
||||
}
|
||||
if((n_button.properties.dynamicY == null || n_button.properties.dynamicY.isEmpty())&&buttonProperties.has("y")) {
|
||||
double buttonC = buttonProperties.getDouble("y");
|
||||
double ratio = buttonC/CallbackBridge.physicalHeight;
|
||||
n_button.properties.dynamicY = ratio + " * ${screen_height}";
|
||||
}
|
||||
layout.mDrawerDataList.add(n_button);
|
||||
}
|
||||
layout.version = 3;
|
||||
return layout;
|
||||
}
|
||||
public static CustomControls convertV1Layout(JSONObject oldLayoutJson) throws JSONException {
|
||||
CustomControls empty = new CustomControls();
|
||||
JSONArray layoutMainArray = oldLayoutJson.getJSONArray("mControlDataList");
|
||||
for(int i = 0; i < layoutMainArray.length(); i++) {
|
||||
JSONObject button = layoutMainArray.getJSONObject(i);
|
||||
ControlData n_button = new ControlData();
|
||||
int[] keycodes = new int[] {LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
|
||||
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
|
||||
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
|
||||
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN};
|
||||
n_button.isDynamicBtn = button.getBoolean("isDynamicBtn");
|
||||
n_button.dynamicX = button.getString("dynamicX");
|
||||
n_button.dynamicY = button.getString("dynamicY");
|
||||
if((n_button.dynamicX == null || n_button.dynamicX.isEmpty())&&button.has("x")) {
|
||||
double buttonC = button.getDouble("x");
|
||||
double ratio = buttonC/CallbackBridge.physicalWidth;
|
||||
n_button.dynamicX = ratio + " * ${screen_width}";
|
||||
}
|
||||
if((n_button.dynamicY == null || n_button.dynamicY.isEmpty())&&button.has("y")) {
|
||||
double buttonC = button.getDouble("y");
|
||||
double ratio = buttonC/CallbackBridge.physicalHeight;
|
||||
n_button.dynamicY = ratio + " * ${screen_height}";
|
||||
}
|
||||
n_button.name = button.getString("name");
|
||||
n_button.opacity = ((float)((button.getInt("transparency")-100)*-1))/100f;
|
||||
n_button.passThruEnabled = button.getBoolean("passThruEnabled");
|
||||
n_button.isToggle = button.getBoolean("isToggle");
|
||||
n_button.setHeight(button.getInt("height"));
|
||||
n_button.setWidth(button.getInt("width"));
|
||||
if(convertLookType) {
|
||||
n_button.strokeColor = 0xdd7f7f7f;
|
||||
n_button.bgColor = 0x807f7f7f;
|
||||
n_button.strokeWidth = 10;
|
||||
}else{
|
||||
n_button.bgColor = 0x4d000000;
|
||||
n_button.strokeWidth = 0;
|
||||
}
|
||||
if(button.getBoolean("isRound")) { n_button.cornerRadius = 35f; }
|
||||
int next_idx = 0;
|
||||
if(button.getBoolean("holdShift")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT; next_idx++; }
|
||||
if(button.getBoolean("holdCtrl")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL; next_idx++; }
|
||||
if(button.getBoolean("holdAlt")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT; next_idx++; }
|
||||
keycodes[next_idx] = button.getInt("keycode");
|
||||
n_button.keycodes = keycodes;
|
||||
empty.mControlDataList.add(n_button);
|
||||
}
|
||||
empty.scaledAt = (float)oldLayoutJson.getDouble("scaledAt");
|
||||
empty.version = 3;
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols;
|
||||
|
||||
|
||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
||||
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
/**
|
||||
* This class is intended for sending characters used in chat via the virtual keyboard
|
||||
*/
|
||||
public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText {
|
||||
public TouchCharInput(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
public TouchCharInput(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.editTextStyle);
|
||||
}
|
||||
public TouchCharInput(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setup();
|
||||
}
|
||||
|
||||
|
||||
private boolean isDoingInternalChanges = false;
|
||||
|
||||
/**
|
||||
* We take the new chars, and send them to the game.
|
||||
* If less chars are present, remove some.
|
||||
* The text is always cleaned up.
|
||||
*/
|
||||
@Override
|
||||
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
|
||||
super.onTextChanged(text, start, lengthBefore, lengthAfter);
|
||||
if(isDoingInternalChanges)return;
|
||||
|
||||
for(int i=0; i< lengthBefore; ++i){
|
||||
CallbackBridge.sendKeycode(LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE, '\u0008', 0, 0, true);
|
||||
}
|
||||
for(int i=start, count = 0; count < lengthAfter; ++i){
|
||||
CallbackBridge.sendChar(text.charAt(i), 0);
|
||||
++count;
|
||||
}
|
||||
|
||||
//Reset the keyboard state
|
||||
if(text.length() < 1) clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When we change from app to app, the keyboard gets disabled.
|
||||
* So, we disable the object
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||
super.onWindowFocusChanged(hasWindowFocus);
|
||||
disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts the back key to disable focus
|
||||
* Does not affect the rest of the activity.
|
||||
*/
|
||||
@Override
|
||||
public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
|
||||
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
|
||||
disable();
|
||||
}
|
||||
return super.onKeyPreIme(keyCode, event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggle on and off the soft keyboard, depending of the state
|
||||
*
|
||||
* @return if the keyboard is set to be shown.
|
||||
*/
|
||||
public boolean switchKeyboardState(){
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
//If an hard keyboard is present, never trigger the soft one
|
||||
if(hasFocus()
|
||||
|| (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY
|
||||
&& getResources().getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES)){
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
clear();
|
||||
disable();
|
||||
return false;
|
||||
}else{
|
||||
enable();
|
||||
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear the EditText from any leftover inputs
|
||||
* It does not affect the in-game input
|
||||
*/
|
||||
@SuppressLint("SetTextI18n")
|
||||
public void clear(){
|
||||
isDoingInternalChanges = true;
|
||||
//Braille space, doesn't trigger keyboard auto-complete
|
||||
//replacing directly the text without though setText avoids notifying changes
|
||||
setText(" ");
|
||||
setSelection(getText().length());
|
||||
isDoingInternalChanges = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the enter key.
|
||||
*/
|
||||
private void sendEnter(){
|
||||
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_ENTER);
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regain ability to exist, take focus and have some text being input
|
||||
*/
|
||||
public void enable(){
|
||||
setEnabled(true);
|
||||
setFocusable(true);
|
||||
setVisibility(VISIBLE);
|
||||
requestFocus();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Lose ability to exist, take focus and have some text being input
|
||||
*/
|
||||
public void disable(){
|
||||
clear();
|
||||
setVisibility(GONE);
|
||||
clearFocus();
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function deals with anything that has to be executed when the constructor is called
|
||||
*/
|
||||
private void setup(){
|
||||
setOnEditorActionListener((textView, i, keyEvent) -> {
|
||||
sendEnter();
|
||||
clear();
|
||||
disable();
|
||||
return false;
|
||||
});
|
||||
clear();
|
||||
disable();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,559 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.buttons;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.*;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.view.View.*;
|
||||
import android.widget.*;
|
||||
|
||||
|
||||
|
||||
import androidx.core.math.MathUtils;
|
||||
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
|
||||
import net.kdt.pojavlaunch.customcontrols.handleview.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
import static net.kdt.pojavlaunch.LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_BUTTONSIZE;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_BOTTOM_OFFSET;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_LEFT_OFFSET;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_RIGHT_OFFSET;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_TOP_OFFSET;
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class ControlButton extends androidx.appcompat.widget.AppCompatButton implements OnLongClickListener
|
||||
{
|
||||
private final Paint mRectPaint = new Paint();;
|
||||
|
||||
protected GestureDetector mGestureDetector;
|
||||
protected ControlData mProperties;
|
||||
protected SelectionEndHandleView mHandleView;
|
||||
|
||||
protected boolean mModifiable = false;
|
||||
protected boolean mCanTriggerLongClick = true;
|
||||
|
||||
protected boolean isToggled = false;
|
||||
protected boolean isPointerOutOfBounds = false;
|
||||
|
||||
public ControlButton(ControlLayout layout, ControlData properties) {
|
||||
super(layout.getContext());
|
||||
setPadding(4, 4, 4, 4);
|
||||
|
||||
setOnLongClickListener(this);
|
||||
|
||||
//When a button is created, the width/height has yet to be processed to fit the scaling.
|
||||
setProperties(preProcessProperties(properties, layout));
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
public HandleView getHandleView() {
|
||||
return mHandleView;
|
||||
}
|
||||
|
||||
public ControlData getProperties() {
|
||||
return mProperties;
|
||||
}
|
||||
|
||||
public void setProperties(ControlData properties) {
|
||||
setProperties(properties, true);
|
||||
}
|
||||
|
||||
public ControlData preProcessProperties(ControlData properties, ControlLayout layout){
|
||||
//When a button is created, properties have to be modified to fit the screen.
|
||||
//Size
|
||||
properties.setWidth(properties.getWidth() / layout.getLayoutScale() * PREF_BUTTONSIZE);
|
||||
properties.setHeight(properties.getHeight() / layout.getLayoutScale() * PREF_BUTTONSIZE);
|
||||
|
||||
//Visibility
|
||||
properties.isHideable = !properties.containsKeycode(ControlData.SPECIALBTN_TOGGLECTRL) && !properties.containsKeycode(ControlData.SPECIALBTN_VIRTUALMOUSE);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperties(ControlData properties, boolean changePos) {
|
||||
mProperties = properties;
|
||||
|
||||
if(mProperties.isToggle){
|
||||
//For the toggle layer
|
||||
final TypedValue value = new TypedValue();
|
||||
getContext().getTheme().resolveAttribute(R.attr.colorAccent, value, true);
|
||||
mRectPaint.setColor(value.data);
|
||||
mRectPaint.setAlpha(128);
|
||||
}else{
|
||||
mRectPaint.setColor(Color.WHITE);
|
||||
mRectPaint.setAlpha(60);
|
||||
}
|
||||
|
||||
setText(properties.name);
|
||||
|
||||
if (changePos) {
|
||||
setX(properties.insertDynamicPos(mProperties.dynamicX));
|
||||
setY(properties.insertDynamicPos(mProperties.dynamicY));
|
||||
}
|
||||
|
||||
setLayoutParams(new FrameLayout.LayoutParams((int) properties.getWidth(), (int) properties.getHeight() ));
|
||||
}
|
||||
|
||||
public void setBackground(){
|
||||
GradientDrawable gd = new GradientDrawable();
|
||||
gd.setColor(mProperties.bgColor);
|
||||
gd.setStroke(computeStrokeWidth(mProperties.strokeWidth), mProperties.strokeColor);
|
||||
gd.setCornerRadius(computeCornerRadius(mProperties.cornerRadius));
|
||||
|
||||
setBackground(gd);
|
||||
}
|
||||
|
||||
public void setModifiable(boolean isModifiable) {
|
||||
mModifiable = isModifiable;
|
||||
}
|
||||
|
||||
private void setModified(boolean modified) {
|
||||
if (getParent() != null)
|
||||
((ControlLayout) getParent()).setModified(modified);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setLayoutParams(ViewGroup.LayoutParams params) {
|
||||
super.setLayoutParams(params);
|
||||
|
||||
mProperties.setWidth(params.width);
|
||||
mProperties.setHeight(params.height);
|
||||
setBackground();
|
||||
|
||||
// Re-calculate position
|
||||
if(!mProperties.isDynamicBtn){
|
||||
setX(getX());
|
||||
setY(getY());
|
||||
}else {
|
||||
setX(mProperties.insertDynamicPos(mProperties.dynamicX));
|
||||
setY(mProperties.insertDynamicPos(mProperties.dynamicY));
|
||||
}
|
||||
|
||||
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
public void setVisible(boolean isVisible){
|
||||
if(mProperties.isHideable)
|
||||
setVisibility(isVisible ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onVisibilityChanged(View changedView, int visibility) {
|
||||
super.onVisibilityChanged(changedView, visibility);
|
||||
setWillNotDraw(visibility == GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setX(float x) {
|
||||
// We have to account for control offset preference
|
||||
if(x + (mProperties.getWidth()/2f) > CallbackBridge.physicalWidth/2f){
|
||||
x -= PREF_CONTROL_RIGHT_OFFSET;
|
||||
}else{
|
||||
x += PREF_CONTROL_LEFT_OFFSET;
|
||||
}
|
||||
|
||||
super.setX(x);
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setY(float y) {
|
||||
// We have to account for control offset preference
|
||||
if(y - PREF_CONTROL_TOP_OFFSET + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
|
||||
y -= PREF_CONTROL_BOTTOM_OFFSET;
|
||||
}else{
|
||||
y += PREF_CONTROL_TOP_OFFSET;
|
||||
}
|
||||
|
||||
super.setY(y);
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getX() {
|
||||
float x = super.getX();
|
||||
// We have to account for control offset preference
|
||||
if(x + (mProperties.getWidth()/2f) > (CallbackBridge.physicalWidth)/2f){
|
||||
x += PREF_CONTROL_RIGHT_OFFSET;
|
||||
}else{
|
||||
x -= PREF_CONTROL_LEFT_OFFSET;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getY(){
|
||||
// We have to account for control offset preference
|
||||
float y = super.getY();
|
||||
if(y + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
|
||||
y += PREF_CONTROL_BOTTOM_OFFSET;
|
||||
}else{
|
||||
y -= PREF_CONTROL_TOP_OFFSET;
|
||||
}
|
||||
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the dynamic equation on the x axis.
|
||||
* @param dynamicX The equation to compute the position from
|
||||
*/
|
||||
public void setDynamicX(String dynamicX){
|
||||
mProperties.dynamicX = dynamicX;
|
||||
setX(mProperties.insertDynamicPos(dynamicX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the dynamic equation on the y axis.
|
||||
* @param dynamicY The equation to compute the position from
|
||||
*/
|
||||
public void setDynamicY(String dynamicY){
|
||||
mProperties.dynamicY = dynamicY;
|
||||
setY(mProperties.insertDynamicPos(dynamicY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a dynamic equation from an absolute position, used to scale properly across devices
|
||||
* @param x The absolute position on the horizontal axis
|
||||
* @return The equation as a String
|
||||
*/
|
||||
public String generateDynamicX(float x){
|
||||
if(x + (mProperties.getWidth()/2f) > CallbackBridge.physicalWidth/2f){
|
||||
return (x + mProperties.getWidth()) / CallbackBridge.physicalWidth + " * ${screen_width} - ${width}";
|
||||
}else{
|
||||
return x / CallbackBridge.physicalWidth + " * ${screen_width}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a dynamic equation from an absolute position, used to scale properly across devices
|
||||
* @param y The absolute position on the vertical axis
|
||||
* @return The equation as a String
|
||||
*/
|
||||
public String generateDynamicY(float y){
|
||||
if(y + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
|
||||
return (y + mProperties.getHeight()) / CallbackBridge.physicalHeight + " * ${screen_height} - ${height}";
|
||||
}else{
|
||||
return y / CallbackBridge.physicalHeight + " * ${screen_height}";
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProperties() {
|
||||
setProperties(mProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (isToggled || (!mProperties.isToggle && isActivated()))
|
||||
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), mProperties.cornerRadius, mProperties.cornerRadius, mRectPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (mCanTriggerLongClick && mModifiable) {
|
||||
//Instantiate on need only
|
||||
if(mHandleView == null) mHandleView = new SelectionEndHandleView(this);
|
||||
|
||||
if (mHandleView.isShowing()) {
|
||||
mHandleView.hide();
|
||||
} else {
|
||||
if (getParent() != null) {
|
||||
((ControlLayout) getParent()).hideAllHandleViews();
|
||||
}
|
||||
|
||||
try {
|
||||
mHandleView.show(this);
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mCanTriggerLongClick;
|
||||
}
|
||||
|
||||
protected float downX, downY;
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if(!mModifiable){
|
||||
mCanTriggerLongClick = false;
|
||||
|
||||
switch (event.getActionMasked()){
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
//Send the event to be taken as a mouse action
|
||||
if(mProperties.passThruEnabled && CallbackBridge.isGrabbing()){
|
||||
MinecraftGLView v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
|
||||
if (v != null) v.dispatchTouchEvent(event);
|
||||
}
|
||||
|
||||
//If out of bounds
|
||||
if(event.getX() < getLeft() || event.getX() > getRight() ||
|
||||
event.getY() < getTop() || event.getY() > getBottom()){
|
||||
if(mProperties.isSwipeable && !isPointerOutOfBounds){
|
||||
//Remove keys
|
||||
if(!triggerToggle()) {
|
||||
sendKeyPresses(false);
|
||||
}
|
||||
}
|
||||
isPointerOutOfBounds = true;
|
||||
((ControlLayout) getParent()).onTouch(this, event);
|
||||
break;
|
||||
}
|
||||
|
||||
//Else if we now are in bounds
|
||||
if(isPointerOutOfBounds) {
|
||||
((ControlLayout) getParent()).onTouch(this, event);
|
||||
//RE-press the button
|
||||
if(mProperties.isSwipeable && !mProperties.isToggle){
|
||||
sendKeyPresses(true);
|
||||
}
|
||||
}
|
||||
isPointerOutOfBounds = false;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_DOWN: // 0
|
||||
case MotionEvent.ACTION_POINTER_DOWN: // 5
|
||||
if(!mProperties.isToggle){
|
||||
sendKeyPresses(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP: // 1
|
||||
case MotionEvent.ACTION_CANCEL: // 3
|
||||
case MotionEvent.ACTION_POINTER_UP: // 6
|
||||
if(mProperties.passThruEnabled){
|
||||
MinecraftGLView v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
|
||||
if (v != null) v.dispatchTouchEvent(event);
|
||||
}
|
||||
if(isPointerOutOfBounds) ((ControlLayout) getParent()).onTouch(this, event);
|
||||
isPointerOutOfBounds = false;
|
||||
|
||||
if(!triggerToggle()) {
|
||||
sendKeyPresses(false);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If the button can be modified/moved */
|
||||
//Instantiate the gesture detector only when needed
|
||||
if(mGestureDetector == null) mGestureDetector = new GestureDetector(getContext(), new SingleTapConfirm());
|
||||
|
||||
if (mGestureDetector.onTouchEvent(event)) {
|
||||
mCanTriggerLongClick = true;
|
||||
onLongClick(this);
|
||||
}
|
||||
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mCanTriggerLongClick = true;
|
||||
downX = event.getRawX() - getX();
|
||||
downY = event.getRawY() - getY();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mCanTriggerLongClick = false;
|
||||
|
||||
if (!mProperties.isDynamicBtn) {
|
||||
snapAndAlign(
|
||||
MathUtils.clamp(event.getRawX() - downX, 0, CallbackBridge.physicalWidth - getWidth()),
|
||||
MathUtils.clamp(event.getRawY() - downY, 0, CallbackBridge.physicalHeight - getHeight())
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passe a series of checks to determine if the ControlButton is available to be snapped on.
|
||||
*
|
||||
* @param button The button to check
|
||||
* @return whether or not the button
|
||||
*/
|
||||
protected boolean canSnap(ControlButton button){
|
||||
float MIN_DISTANCE = Tools.dpToPx(8);
|
||||
|
||||
if(button == this) return false;
|
||||
if(com.google.android.material.math.MathUtils.dist(
|
||||
button.getX() + button.getWidth()/2f,
|
||||
button.getY() + button.getHeight()/2f,
|
||||
getX() + getWidth()/2f,
|
||||
getY() + getHeight()/2f) > Math.max(button.getWidth()/2f + getWidth()/2f, button.getHeight()/2f + getHeight()/2f) + MIN_DISTANCE) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to snap, then align to neighboring buttons, given the provided coordinates.
|
||||
* The new position is automatically applied to the View,
|
||||
* regardless of if the View snapped or not.
|
||||
*
|
||||
* The new position is always dynamic, thus replacing previous dynamic positions
|
||||
*
|
||||
* @param x Coordinate on the x axis
|
||||
* @param y Coordinate on the y axis
|
||||
*/
|
||||
protected void snapAndAlign(float x, float y){
|
||||
float MIN_DISTANCE = Tools.dpToPx(8);
|
||||
String dynamicX = generateDynamicX(x);
|
||||
String dynamicY = generateDynamicY(y);
|
||||
|
||||
setX(x);
|
||||
setY(y);
|
||||
|
||||
for(ControlButton button : ((ControlLayout) getParent()).getButtonChildren()){
|
||||
//Step 1: Filter unwanted buttons
|
||||
if(!canSnap(button)) continue;
|
||||
|
||||
//Step 2: Get Coordinates
|
||||
float button_top = button.getY();
|
||||
float button_bottom = button_top + button.getHeight();
|
||||
float button_left = button.getX();
|
||||
float button_right = button_left + button.getWidth();
|
||||
|
||||
float top = getY();
|
||||
float bottom = getY() + getHeight();
|
||||
float left = getX();
|
||||
float right = getX() + getWidth();
|
||||
|
||||
//Step 3: For each axis, we try to snap to the nearest
|
||||
if(Math.abs(top - button_bottom) < MIN_DISTANCE){ // Bottom snap
|
||||
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " + ${margin}" ;
|
||||
}else if(Math.abs(button_top - bottom) < MIN_DISTANCE){ //Top snap
|
||||
dynamicY = applySize(button.getProperties().dynamicY, button) + " - ${height} - ${margin}";
|
||||
}
|
||||
if(!dynamicY.equals(generateDynamicY(getY()))){ //If we snapped
|
||||
if(Math.abs(button_left - left) < MIN_DISTANCE){ //Left align snap
|
||||
dynamicX = applySize(button.getProperties().dynamicX, button);
|
||||
}else if(Math.abs(button_right - right) < MIN_DISTANCE){ //Right align snap
|
||||
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " - ${width}";
|
||||
}
|
||||
}
|
||||
|
||||
if(Math.abs(button_left - right) < MIN_DISTANCE){ //Left snap
|
||||
dynamicX = applySize(button.getProperties().dynamicX, button) + " - ${width} - ${margin}";
|
||||
}else if(Math.abs(left - button_right) < MIN_DISTANCE){ //Right snap
|
||||
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " + ${margin}";
|
||||
}
|
||||
if(!dynamicX.equals(generateDynamicX(getX()))){ //If we snapped
|
||||
if(Math.abs(button_top - top) < MIN_DISTANCE){ //Top align snap
|
||||
dynamicY = applySize(button.getProperties().dynamicY, button);
|
||||
}else if(Math.abs(button_bottom - bottom) < MIN_DISTANCE){ //Bottom align snap
|
||||
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " - ${height}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setDynamicX(dynamicX);
|
||||
setDynamicY(dynamicY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a pre-conversion of an equation using values from a button,
|
||||
* so the variables can be used for another button
|
||||
*
|
||||
* Internal use only.
|
||||
* @param equation The dynamic position as a String
|
||||
* @param button The button to get the values from.
|
||||
* @return The pre-processed equation as a String.
|
||||
*/
|
||||
private static String applySize(String equation, ControlButton button){
|
||||
return equation
|
||||
.replace("${right}", "(${screen_width} - ${width})")
|
||||
.replace("${bottom}","(${screen_height} - ${height})")
|
||||
.replace("${height}", "(px(" + Tools.pxToDp(button.getProperties().getHeight()) + ") /" + PREF_BUTTONSIZE + " * ${preferred_scale})")
|
||||
.replace("${width}", "(px(" + Tools.pxToDp(button.getProperties().getWidth()) + ") / " + PREF_BUTTONSIZE + " * ${preferred_scale})");
|
||||
}
|
||||
|
||||
public int computeStrokeWidth(float widthInPercent){
|
||||
float maxSize = Math.max(mProperties.getWidth(), mProperties.getHeight());
|
||||
return (int)((maxSize/2) * (widthInPercent/100));
|
||||
}
|
||||
|
||||
public float computeCornerRadius(float radiusInPercent){
|
||||
float minSize = Math.min(mProperties.getWidth(), mProperties.getHeight());
|
||||
return (minSize/2) * (radiusInPercent/100);
|
||||
}
|
||||
|
||||
public boolean triggerToggle(){
|
||||
//returns true a the toggle system is triggered
|
||||
if(mProperties.isToggle){
|
||||
isToggled = !isToggled;
|
||||
invalidate();
|
||||
sendKeyPresses(isToggled);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void sendKeyPresses(boolean isDown){
|
||||
setActivated(isDown);
|
||||
for(int keycode : mProperties.keycodes){
|
||||
if(keycode >= GLFW_KEY_UNKNOWN){
|
||||
sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
|
||||
CallbackBridge.setModifiers(keycode, isDown);
|
||||
}else{
|
||||
sendSpecialKey(keycode, isDown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSpecialKey(int keycode, boolean isDown){
|
||||
switch (keycode) {
|
||||
case ControlData.SPECIALBTN_KEYBOARD:
|
||||
if(isDown)BaseMainActivity.switchKeyboardState();
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_TOGGLECTRL:
|
||||
if(isDown)MainActivity.mControlLayout.toggleControlVisible();
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_VIRTUALMOUSE:
|
||||
if(isDown)BaseMainActivity.toggleMouse(getContext());
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_MOUSEPRI:
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_MOUSEMID:
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_MIDDLE, isDown);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_MOUSESEC:
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_SCROLLDOWN:
|
||||
if (!isDown) CallbackBridge.sendScroll(0, 1d);
|
||||
break;
|
||||
|
||||
case ControlData.SPECIALBTN_SCROLLUP:
|
||||
if (!isDown) CallbackBridge.sendScroll(0, -1d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.buttons;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class ControlDrawer extends ControlButton {
|
||||
|
||||
|
||||
public ArrayList<ControlSubButton> buttons;
|
||||
public ControlDrawerData drawerData;
|
||||
public ControlLayout mLayout;
|
||||
public boolean areButtonsVisible;
|
||||
|
||||
|
||||
public ControlDrawer(ControlLayout layout, ControlDrawerData drawerData) {
|
||||
super(layout, drawerData.properties);
|
||||
|
||||
buttons = new ArrayList<>(drawerData.buttonProperties.size());
|
||||
mLayout = layout;
|
||||
this.drawerData = drawerData;
|
||||
areButtonsVisible = layout.getModifiable();
|
||||
}
|
||||
|
||||
|
||||
public void addButton(ControlData properties){
|
||||
addButton(new ControlSubButton(mLayout, properties, this));
|
||||
}
|
||||
|
||||
public void addButton(ControlSubButton button){
|
||||
buttons.add(button);
|
||||
setControlButtonVisibility(button, mModifiable || areButtonsVisible);
|
||||
syncButtons();
|
||||
}
|
||||
|
||||
private void setControlButtonVisibility(ControlButton button, boolean isVisible){
|
||||
button.setVisible(isVisible);
|
||||
}
|
||||
|
||||
private void switchButtonVisibility(){
|
||||
areButtonsVisible = !areButtonsVisible;
|
||||
for(ControlButton button : buttons){
|
||||
button.setVisible(areButtonsVisible);
|
||||
}
|
||||
}
|
||||
|
||||
//Syncing stuff
|
||||
private void alignButtons(){
|
||||
|
||||
if(buttons == null) return;
|
||||
if(drawerData.orientation == ControlDrawerData.Orientation.FREE) return;
|
||||
for(int i=0; i < buttons.size(); ++i){
|
||||
switch (drawerData.orientation){
|
||||
case RIGHT:
|
||||
buttons.get(i).setDynamicX(generateDynamicX(getX() + (drawerData.properties.getWidth() + Tools.dpToPx(2))*(i+1) ));
|
||||
buttons.get(i).setDynamicY(generateDynamicY(getY()));
|
||||
break;
|
||||
|
||||
case LEFT:
|
||||
buttons.get(i).setDynamicX(generateDynamicX(getX() - (drawerData.properties.getWidth() + Tools.dpToPx(2))*(i+1)));
|
||||
buttons.get(i).setDynamicY(generateDynamicY(getY()));
|
||||
break;
|
||||
|
||||
case UP:
|
||||
buttons.get(i).setDynamicY(generateDynamicY(getY() - (drawerData.properties.getHeight() + Tools.dpToPx(2))*(i+1)));
|
||||
buttons.get(i).setDynamicX(generateDynamicX(getX()));
|
||||
break;
|
||||
|
||||
case DOWN:
|
||||
buttons.get(i).setDynamicY(generateDynamicY(getY() + (drawerData.properties.getHeight() + Tools.dpToPx(2))*(i+1)));
|
||||
buttons.get(i).setDynamicX(generateDynamicX(getX()));
|
||||
break;
|
||||
}
|
||||
buttons.get(i).updateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void resizeButtons(){
|
||||
if (buttons == null) return;
|
||||
for(ControlSubButton subButton : buttons){
|
||||
subButton.mProperties.setWidth(mProperties.getWidth());
|
||||
subButton.mProperties.setHeight(mProperties.getHeight());
|
||||
|
||||
subButton.updateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public void syncButtons(){
|
||||
alignButtons();
|
||||
resizeButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not the button passed as a parameter belongs to this drawer.
|
||||
*
|
||||
* @param button The button to look for
|
||||
* @return Whether the button is in the buttons list of the drawer.
|
||||
*/
|
||||
public boolean containsChild(ControlButton button){
|
||||
for(ControlButton childButton : buttons){
|
||||
if (childButton == button) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlData preProcessProperties(ControlData properties, ControlLayout layout) {
|
||||
ControlData data = super.preProcessProperties(properties, layout);
|
||||
data.isHideable = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean isVisible) {
|
||||
//TODO replicate changes to his children ?
|
||||
setVisibility(isVisible ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if(!mModifiable){
|
||||
switch (event.getActionMasked()){
|
||||
case MotionEvent.ACTION_UP: // 1
|
||||
case MotionEvent.ACTION_POINTER_UP: // 6
|
||||
switchButtonVisibility();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canSnap(ControlButton button) {
|
||||
return super.canSnap(button) && !containsChild(button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setX(float x) {
|
||||
super.setX(x);
|
||||
alignButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setY(float y) {
|
||||
super.setY(y);
|
||||
alignButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLayoutParams(ViewGroup.LayoutParams params) {
|
||||
super.setLayoutParams(params);
|
||||
syncButtons();
|
||||
}
|
||||
|
||||
//Getters
|
||||
public ControlDrawerData getDrawerData() {
|
||||
return drawerData;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.buttons;
|
||||
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import net.kdt.pojavlaunch.SingleTapConfirm;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
|
||||
|
||||
public class ControlSubButton extends ControlButton {
|
||||
|
||||
public ControlDrawer parentDrawer;
|
||||
|
||||
public ControlSubButton(ControlLayout layout, ControlData properties, ControlDrawer parentDrawer) {
|
||||
super(layout, properties);
|
||||
this.parentDrawer = parentDrawer;
|
||||
|
||||
|
||||
|
||||
filterProperties();
|
||||
}
|
||||
|
||||
private void filterProperties(){
|
||||
mProperties.setHeight(parentDrawer.getProperties().getHeight());
|
||||
mProperties.setWidth(parentDrawer.getProperties().getWidth());
|
||||
mProperties.isDynamicBtn = false;
|
||||
|
||||
setProperties(mProperties, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean isVisible) {
|
||||
setVisibility(isVisible ? (parentDrawer.areButtonsVisible ? VISIBLE : GONE) : (!mProperties.isHideable && parentDrawer.getVisibility() == GONE) ? VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLayoutParams(ViewGroup.LayoutParams params) {
|
||||
if(parentDrawer != null){
|
||||
params.width = (int)parentDrawer.mProperties.getWidth();
|
||||
params.height = (int)parentDrawer.mProperties.getHeight();
|
||||
}
|
||||
super.setLayoutParams(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if(!mModifiable || parentDrawer.drawerData.orientation == ControlDrawerData.Orientation.FREE){
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
if(mGestureDetector == null) mGestureDetector = new GestureDetector(getContext(), new SingleTapConfirm());
|
||||
|
||||
if (mGestureDetector.onTouchEvent(event)) {
|
||||
mCanTriggerLongClick = true;
|
||||
onLongClick(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,412 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Choreographer;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import androidx.core.math.MathUtils;
|
||||
|
||||
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_EAST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NONE;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH_EAST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH_WEST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH_EAST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH_WEST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_WEST;
|
||||
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.isJoystickEvent;
|
||||
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
|
||||
|
||||
public class Gamepad {
|
||||
|
||||
/* Resolution scaler option, allow downsizing a window */
|
||||
private final float scaleFactor = LauncherPreferences.DEFAULT_PREF.getInt("resolutionRatio",100)/100f;
|
||||
/* Mouse positions, scaled by the scaleFactor */
|
||||
private float mouse_x, mouse_y;
|
||||
/* Sensitivity, adjusted according to screen size */
|
||||
private final double sensitivityFactor = (1.4 * (1080f/ currentDisplayMetrics.heightPixels));
|
||||
|
||||
private final ImageView pointerView;
|
||||
|
||||
private final GamepadDpad gamepadDpad = new GamepadDpad();
|
||||
|
||||
private final GamepadJoystick leftJoystick;
|
||||
private int currentJoystickDirection = DIRECTION_NONE;
|
||||
|
||||
private final GamepadJoystick rightJoystick;
|
||||
private float lastHorizontalValue = 0.0f;
|
||||
private float lastVerticalValue = 0.0f;
|
||||
|
||||
private final double mouseMaxAcceleration = 2f;
|
||||
|
||||
private double mouseMagnitude;
|
||||
private double mouseAngle;
|
||||
private double mouseSensitivity = 19;
|
||||
|
||||
private final GamepadMap gameMap = GamepadMap.getDefaultGameMap();
|
||||
private final GamepadMap menuMap = GamepadMap.getDefaultMenuMap();
|
||||
private GamepadMap currentMap = gameMap;
|
||||
|
||||
private boolean lastGrabbingState = true;
|
||||
private final boolean mModifierDigitalTriggers;
|
||||
private boolean mModifierSwappedAxis = true; //Triggers and right stick axis are swapped.
|
||||
|
||||
private final Choreographer screenChoreographer;
|
||||
private long lastFrameTime;
|
||||
|
||||
public Gamepad(View contextView, InputDevice inputDevice){
|
||||
screenChoreographer = Choreographer.getInstance();
|
||||
Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
updateGrabbingState();
|
||||
tick(frameTimeNanos);
|
||||
screenChoreographer.postFrameCallback(this);
|
||||
}
|
||||
};
|
||||
screenChoreographer.postFrameCallback(frameCallback);
|
||||
lastFrameTime = System.nanoTime();
|
||||
|
||||
Toast.makeText(contextView.getContext(),"GAMEPAD CREATED", Toast.LENGTH_LONG).show();
|
||||
for(InputDevice.MotionRange range : inputDevice.getMotionRanges()){
|
||||
if(range.getAxis() == MotionEvent.AXIS_RTRIGGER
|
||||
|| range.getAxis() == MotionEvent.AXIS_LTRIGGER
|
||||
|| range.getAxis() == MotionEvent.AXIS_GAS
|
||||
|| range.getAxis() == MotionEvent.AXIS_BRAKE){
|
||||
mModifierSwappedAxis = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
leftJoystick = new GamepadJoystick(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, inputDevice);
|
||||
if(!mModifierSwappedAxis)
|
||||
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, inputDevice);
|
||||
else
|
||||
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_RX, MotionEvent.AXIS_RY, inputDevice);
|
||||
|
||||
mModifierDigitalTriggers = inputDevice.hasKeys(KeyEvent.KEYCODE_BUTTON_R2)[0];
|
||||
|
||||
Context ctx = contextView.getContext();
|
||||
pointerView = new ImageView(contextView.getContext());
|
||||
pointerView.setImageDrawable(ResourcesCompat.getDrawable(ctx.getResources(), R.drawable.pointer, ctx.getTheme()));
|
||||
pointerView.getDrawable().setFilterBitmap(false);
|
||||
|
||||
int size = (int) ((22 * getMcScale()) / scaleFactor);
|
||||
pointerView.setLayoutParams(new FrameLayout.LayoutParams(size, size));
|
||||
|
||||
mouse_x = CallbackBridge.windowWidth/2;
|
||||
mouse_y = CallbackBridge.windowHeight/2;
|
||||
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
|
||||
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
|
||||
|
||||
((ViewGroup)contextView.getParent()).addView(pointerView);
|
||||
}
|
||||
|
||||
|
||||
public void tick(long frameTimeNanos){
|
||||
//update mouse position
|
||||
if(lastHorizontalValue != 0 || lastVerticalValue != 0){
|
||||
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
|
||||
|
||||
double acceleration = (mouseMagnitude - currentJoystick.getDeadzone()) / (1 - currentJoystick.getDeadzone());
|
||||
acceleration = Math.pow(acceleration, mouseMaxAcceleration);
|
||||
if(acceleration > 1) acceleration = 1;
|
||||
|
||||
// Compute delta since last tick time
|
||||
float deltaX = (float) (Math.cos(mouseAngle) * acceleration * mouseSensitivity);
|
||||
float deltaY = (float) (Math.sin(mouseAngle) * acceleration * mouseSensitivity);
|
||||
float deltaTimeScale = ((frameTimeNanos - lastFrameTime) / 16666666f); // Scale of 1 = 60Hz
|
||||
deltaX *= deltaTimeScale;
|
||||
deltaY *= deltaTimeScale;
|
||||
|
||||
CallbackBridge.mouseX += deltaX;
|
||||
CallbackBridge.mouseY -= deltaY;
|
||||
|
||||
if(!lastGrabbingState){
|
||||
CallbackBridge.mouseX = MathUtils.clamp(CallbackBridge.mouseX, 0, CallbackBridge.windowWidth);
|
||||
CallbackBridge.mouseY = MathUtils.clamp(CallbackBridge.mouseY, 0, CallbackBridge.windowHeight);
|
||||
placePointerView((int) (CallbackBridge.mouseX / scaleFactor), (int) (CallbackBridge.mouseY/ scaleFactor));
|
||||
}
|
||||
|
||||
mouse_x = CallbackBridge.mouseX;
|
||||
mouse_y = CallbackBridge.mouseY;
|
||||
|
||||
//Send the mouse to the game
|
||||
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
|
||||
}
|
||||
|
||||
// Update last nano time
|
||||
lastFrameTime = frameTimeNanos;
|
||||
}
|
||||
|
||||
/** Update the grabbing state, and change the currentMap, mouse position and sensibility */
|
||||
private void updateGrabbingState() {
|
||||
boolean lastGrabbingValue = lastGrabbingState;
|
||||
lastGrabbingState = CallbackBridge.isGrabbing();
|
||||
if(lastGrabbingValue == lastGrabbingState) return;
|
||||
|
||||
// Switch grabbing state then
|
||||
currentMap.resetPressedState();
|
||||
if(lastGrabbingState){
|
||||
currentMap = gameMap;
|
||||
pointerView.setVisibility(View.INVISIBLE);
|
||||
mouseSensitivity = 18;
|
||||
return;
|
||||
}
|
||||
|
||||
currentMap = menuMap;
|
||||
sendDirectionalKeycode(currentJoystickDirection, false, gameMap); // removing what we were doing
|
||||
|
||||
mouse_x = CallbackBridge.windowWidth/2;
|
||||
mouse_y = CallbackBridge.windowHeight/2;
|
||||
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
|
||||
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
|
||||
pointerView.setVisibility(View.VISIBLE);
|
||||
// Sensitivity in menu is MC and HARDWARE resolution dependent
|
||||
mouseSensitivity = 19 * scaleFactor / sensitivityFactor;
|
||||
|
||||
}
|
||||
|
||||
public void update(KeyEvent event){
|
||||
sendButton(event);
|
||||
}
|
||||
|
||||
public void update(MotionEvent event){
|
||||
updateDirectionalJoystick(event);
|
||||
updateMouseJoystick(event);
|
||||
updateAnalogTriggers(event);
|
||||
|
||||
int[] dpadEvent = gamepadDpad.convertEvent(event);
|
||||
sendButton(dpadEvent[0], dpadEvent[1]);
|
||||
}
|
||||
|
||||
private void updateMouseJoystick(MotionEvent event){
|
||||
GamepadJoystick currentJoystick = lastGrabbingState ? rightJoystick : leftJoystick;
|
||||
float horizontalValue = currentJoystick.getHorizontalAxis(event);
|
||||
float verticalValue = currentJoystick.getVerticalAxis(event);
|
||||
if(horizontalValue != lastHorizontalValue || verticalValue != lastVerticalValue){
|
||||
lastHorizontalValue = horizontalValue;
|
||||
lastVerticalValue = verticalValue;
|
||||
|
||||
mouseMagnitude = currentJoystick.getMagnitude(event);
|
||||
mouseAngle = currentJoystick.getAngleRadian(event);
|
||||
|
||||
tick(System.nanoTime());
|
||||
return;
|
||||
}
|
||||
lastHorizontalValue = horizontalValue;
|
||||
lastVerticalValue = verticalValue;
|
||||
|
||||
mouseMagnitude = currentJoystick.getMagnitude(event);
|
||||
mouseAngle = currentJoystick.getAngleRadian(event);
|
||||
|
||||
}
|
||||
|
||||
private void updateDirectionalJoystick(MotionEvent event){
|
||||
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
|
||||
|
||||
int lastJoystickDirection = currentJoystickDirection;
|
||||
currentJoystickDirection = currentJoystick.getHeightDirection(event);
|
||||
|
||||
if(currentJoystickDirection == lastJoystickDirection) return;
|
||||
|
||||
sendDirectionalKeycode(lastJoystickDirection, false, getCurrentMap());
|
||||
sendDirectionalKeycode(currentJoystickDirection, true, getCurrentMap());
|
||||
}
|
||||
|
||||
private void updateAnalogTriggers(MotionEvent event){
|
||||
if(!mModifierDigitalTriggers){
|
||||
getCurrentMap().TRIGGER_LEFT.update(
|
||||
(event.getAxisValue(MotionEvent.AXIS_LTRIGGER) > 0.5)
|
||||
|| (event.getAxisValue(MotionEvent.AXIS_BRAKE) > 0.5)
|
||||
|| (mModifierSwappedAxis &&(event.getAxisValue(MotionEvent.AXIS_Z) > 0.5)) );
|
||||
getCurrentMap().TRIGGER_RIGHT.update(
|
||||
(event.getAxisValue( MotionEvent.AXIS_RTRIGGER) > 0.5)
|
||||
|| (event.getAxisValue(MotionEvent.AXIS_GAS) > 0.5)
|
||||
|| (mModifierSwappedAxis && event.getAxisValue(MotionEvent.AXIS_RZ) > 0.5) );
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyGUISizeChange(int newSize){
|
||||
//Change the pointer size to match UI
|
||||
int size = (int) ((22 * newSize) / scaleFactor);
|
||||
pointerView.post(() -> pointerView.setLayoutParams(new FrameLayout.LayoutParams(size, size)));
|
||||
|
||||
}
|
||||
|
||||
private GamepadMap getCurrentMap(){
|
||||
return currentMap;
|
||||
}
|
||||
|
||||
private static void sendDirectionalKeycode(int direction, boolean isDown, GamepadMap map){
|
||||
switch (direction){
|
||||
case DIRECTION_NORTH:
|
||||
sendInput(map.DIRECTION_FORWARD, isDown);
|
||||
break;
|
||||
case DIRECTION_NORTH_EAST:
|
||||
sendInput(map.DIRECTION_FORWARD, isDown);
|
||||
sendInput(map.DIRECTION_RIGHT, isDown);
|
||||
break;
|
||||
case DIRECTION_EAST:
|
||||
sendInput(map.DIRECTION_RIGHT, isDown);
|
||||
break;
|
||||
case DIRECTION_SOUTH_EAST:
|
||||
sendInput(map.DIRECTION_RIGHT, isDown);
|
||||
sendInput(map.DIRECTION_BACKWARD, isDown);
|
||||
break;
|
||||
case DIRECTION_SOUTH:
|
||||
sendInput(map.DIRECTION_BACKWARD, isDown);
|
||||
break;
|
||||
case DIRECTION_SOUTH_WEST:
|
||||
sendInput(map.DIRECTION_BACKWARD, isDown);
|
||||
sendInput(map.DIRECTION_LEFT, isDown);
|
||||
break;
|
||||
case DIRECTION_WEST:
|
||||
sendInput(map.DIRECTION_LEFT, isDown);
|
||||
break;
|
||||
case DIRECTION_NORTH_WEST:
|
||||
sendInput(map.DIRECTION_FORWARD, isDown);
|
||||
sendInput(map.DIRECTION_LEFT, isDown);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void placePointerView(int x, int y){
|
||||
pointerView.setX(x - pointerView.getWidth()/2);
|
||||
pointerView.setY(y - pointerView.getHeight()/2);
|
||||
}
|
||||
|
||||
public void sendButton(KeyEvent event){
|
||||
sendButton(event.getKeyCode(), event.getAction());
|
||||
}
|
||||
|
||||
public void sendButton(int keycode, int action){
|
||||
boolean isDown = action == KeyEvent.ACTION_DOWN;
|
||||
switch (keycode){
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
getCurrentMap().BUTTON_A.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
getCurrentMap().BUTTON_B.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
getCurrentMap().BUTTON_X.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
getCurrentMap().BUTTON_Y.update(isDown);
|
||||
break;
|
||||
|
||||
//Shoulders
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
getCurrentMap().SHOULDER_LEFT.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
getCurrentMap().SHOULDER_RIGHT.update(isDown);
|
||||
break;
|
||||
|
||||
//Triggers
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
getCurrentMap().TRIGGER_LEFT.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
getCurrentMap().TRIGGER_RIGHT.update(isDown);
|
||||
break;
|
||||
|
||||
//L3 || R3
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
getCurrentMap().THUMBSTICK_LEFT.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
getCurrentMap().THUMBSTICK_RIGHT.update(isDown);
|
||||
break;
|
||||
|
||||
//DPAD
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
getCurrentMap().DPAD_UP.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
getCurrentMap().DPAD_DOWN.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
getCurrentMap().DPAD_LEFT.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
getCurrentMap().DPAD_RIGHT.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
getCurrentMap().DPAD_RIGHT.update(false);
|
||||
getCurrentMap().DPAD_LEFT.update(false);
|
||||
getCurrentMap().DPAD_UP.update(false);
|
||||
getCurrentMap().DPAD_DOWN.update(false);
|
||||
break;
|
||||
|
||||
//Start/select
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
getCurrentMap().BUTTON_START.update(isDown);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
getCurrentMap().BUTTON_SELECT.update(isDown);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_SPACE, CallbackBridge.getCurrentMods(), isDown);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendInput(int[] keycodes, boolean isDown){
|
||||
for(int keycode : keycodes){
|
||||
switch (keycode){
|
||||
case GamepadMap.MOUSE_SCROLL_DOWN:
|
||||
if(isDown) CallbackBridge.sendScroll(0, -1);
|
||||
break;
|
||||
case GamepadMap.MOUSE_SCROLL_UP:
|
||||
if(isDown) CallbackBridge.sendScroll(0, 1);
|
||||
break;
|
||||
|
||||
case LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT:
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
|
||||
break;
|
||||
case LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT:
|
||||
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
|
||||
break;
|
||||
}
|
||||
CallbackBridge.setModifiers(keycode, isDown);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static boolean isGamepadEvent(MotionEvent event){
|
||||
return isJoystickEvent(event);
|
||||
}
|
||||
|
||||
public static boolean isGamepadEvent(KeyEvent event){
|
||||
return ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
|
||||
|| GamepadDpad.isDpadEvent(event) );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
public class GamepadButton {
|
||||
|
||||
/*
|
||||
Just a simple button, that auto deal with the great habit from android to just SPAAAMS input events
|
||||
*/
|
||||
public int[] keycodes;
|
||||
public boolean isToggleable = false;
|
||||
private boolean isDown = false;
|
||||
private boolean isToggled = false;
|
||||
|
||||
public void update(KeyEvent event){
|
||||
boolean isKeyDown = (event.getAction() == KeyEvent.ACTION_DOWN);
|
||||
update(isKeyDown);
|
||||
}
|
||||
|
||||
public void update(boolean isKeyDown){
|
||||
if(isKeyDown != isDown){
|
||||
isDown = isKeyDown;
|
||||
if(isToggleable){
|
||||
if(isKeyDown){
|
||||
isToggled = !isToggled;
|
||||
Gamepad.sendInput(keycodes, isToggled);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Gamepad.sendInput(keycodes, isDown);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetButtonState(){
|
||||
if(isDown || isToggled){
|
||||
Gamepad.sendInput(keycodes, false);
|
||||
}
|
||||
isDown = false;
|
||||
isToggled = false;
|
||||
}
|
||||
|
||||
public boolean isDown(){
|
||||
return isToggleable ? isToggled : isDown;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import static android.view.InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
|
||||
|
||||
|
||||
public class GamepadDpad {
|
||||
private int lastKeycode = KEYCODE_DPAD_CENTER;
|
||||
|
||||
/**
|
||||
* Convert the event to a 2 int array: keycode and keyAction, similar to a keyEvent
|
||||
* @param event The motion to convert
|
||||
* @return int[0] keycode, int[1] keyAction
|
||||
*/
|
||||
public int[] convertEvent(MotionEvent event){
|
||||
// Use the hat axis value to find the D-pad direction
|
||||
float xaxis = event.getAxisValue(MotionEvent.AXIS_HAT_X);
|
||||
float yaxis = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
|
||||
int action = KeyEvent.ACTION_DOWN;
|
||||
|
||||
// Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
|
||||
// LEFT and RIGHT direction accordingly.
|
||||
if (Float.compare(xaxis, -1.0f) == 0) {
|
||||
lastKeycode = KEYCODE_DPAD_LEFT;
|
||||
} else if (Float.compare(xaxis, 1.0f) == 0) {
|
||||
lastKeycode = KEYCODE_DPAD_RIGHT;
|
||||
}
|
||||
// Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
|
||||
// UP and DOWN direction accordingly.
|
||||
else if (Float.compare(yaxis, -1.0f) == 0) {
|
||||
lastKeycode = KEYCODE_DPAD_UP;
|
||||
} else if (Float.compare(yaxis, 1.0f) == 0) {
|
||||
lastKeycode = KEYCODE_DPAD_DOWN;
|
||||
}else {
|
||||
//No keycode change
|
||||
action = KeyEvent.ACTION_UP;
|
||||
}
|
||||
|
||||
return new int[]{lastKeycode, action};
|
||||
|
||||
}
|
||||
|
||||
public static boolean isDpadEvent(MotionEvent event) {
|
||||
// Check that input comes from a device with directional pads.
|
||||
// And... also the joystick since it declares sometimes as a joystick.
|
||||
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK;
|
||||
}
|
||||
|
||||
public static boolean isDpadEvent(KeyEvent event){
|
||||
return ((event.getSource() & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) && (event.getDevice().getKeyboardType() == KEYBOARD_TYPE_NON_ALPHABETIC);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
|
||||
import com.google.android.material.math.MathUtils;
|
||||
|
||||
public class GamepadJoystick {
|
||||
|
||||
//Directions
|
||||
public static final int DIRECTION_NONE = -1; //GamepadJoystick at the center
|
||||
|
||||
public static final int DIRECTION_EAST = 0;
|
||||
public static final int DIRECTION_NORTH_EAST = 1;
|
||||
public static final int DIRECTION_NORTH = 2;
|
||||
public static final int DIRECTION_NORTH_WEST = 3;
|
||||
public static final int DIRECTION_WEST = 4;
|
||||
public static final int DIRECTION_SOUTH_WEST = 5;
|
||||
public static final int DIRECTION_SOUTH = 6;
|
||||
public static final int DIRECTION_SOUTH_EAST = 7;
|
||||
|
||||
private final InputDevice device;
|
||||
|
||||
private final int verticalAxis;
|
||||
private final int horizontalAxis;
|
||||
|
||||
public GamepadJoystick(int horizontalAxis, int verticalAxis, InputDevice device){
|
||||
this.verticalAxis = verticalAxis;
|
||||
this.horizontalAxis = horizontalAxis;
|
||||
this.device = device;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public double getAngleRadian(MotionEvent event){
|
||||
//From -PI to PI
|
||||
return -Math.atan2(getVerticalAxis(event), getHorizontalAxis(event));
|
||||
}
|
||||
|
||||
|
||||
public double getAngleDegree(MotionEvent event){
|
||||
//From 0 to 360 degrees
|
||||
double result = Math.toDegrees(getAngleRadian(event));
|
||||
if(result < 0) result += 360;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public double getMagnitude(MotionEvent event){
|
||||
float x = Math.abs(event.getAxisValue(horizontalAxis));
|
||||
float y = Math.abs(event.getAxisValue(verticalAxis));
|
||||
|
||||
return MathUtils.dist(0,0, x, y);
|
||||
}
|
||||
|
||||
public float getVerticalAxis(MotionEvent event){
|
||||
return applyDeadzone(event, verticalAxis);
|
||||
}
|
||||
|
||||
public float getHorizontalAxis(MotionEvent event){
|
||||
return applyDeadzone(event, horizontalAxis);
|
||||
}
|
||||
|
||||
private float applyDeadzone(MotionEvent event, int axis){
|
||||
//This piece of code also modifies the value
|
||||
//to make it seem like there was no deadzone in the first place
|
||||
|
||||
double magnitude = getMagnitude(event);
|
||||
float deadzone = getDeadzone();
|
||||
if (magnitude < deadzone) return 0;
|
||||
|
||||
return (float) ( (event.getAxisValue(axis) / magnitude) * ((magnitude - deadzone) / (1 - deadzone)) );
|
||||
}
|
||||
|
||||
public static boolean isJoystickEvent(MotionEvent event){
|
||||
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
|
||||
&& event.getAction() == MotionEvent.ACTION_MOVE;
|
||||
}
|
||||
|
||||
|
||||
public int getHeightDirection(MotionEvent event){
|
||||
if(getMagnitude(event) <= getDeadzone()) return DIRECTION_NONE;
|
||||
return ((int) ((getAngleDegree(event)+22.5)/45)) % 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the deadzone from the Input device linked to this joystick
|
||||
* Some controller aren't supported, fallback to 0.2 if that the case.
|
||||
* @return the deadzone of the joystick
|
||||
*/
|
||||
public float getDeadzone() {
|
||||
try{
|
||||
return Math.max(device.getMotionRange(horizontalAxis).getFlat() * 1.9f, 0.2f);
|
||||
}catch (Exception e){
|
||||
return 0.2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.gamepad;
|
||||
|
||||
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
|
||||
|
||||
public class GamepadMap {
|
||||
|
||||
public static final int MOUSE_SCROLL_DOWN = -1;
|
||||
public static final int MOUSE_SCROLL_UP = -2;
|
||||
|
||||
/*
|
||||
This class is just here to store the mapping
|
||||
can be modified to create re-mappable controls I guess
|
||||
|
||||
Be warned, you should define ALL keys if you want to avoid a non defined exception
|
||||
*/
|
||||
|
||||
public GamepadButton BUTTON_A = new GamepadButton();
|
||||
public GamepadButton BUTTON_B = new GamepadButton();
|
||||
public GamepadButton BUTTON_X = new GamepadButton();
|
||||
public GamepadButton BUTTON_Y = new GamepadButton();
|
||||
|
||||
public GamepadButton BUTTON_START = new GamepadButton();
|
||||
public GamepadButton BUTTON_SELECT = new GamepadButton();
|
||||
|
||||
public GamepadButton TRIGGER_RIGHT = new GamepadButton(); //R2
|
||||
public GamepadButton TRIGGER_LEFT = new GamepadButton(); //L2
|
||||
|
||||
public GamepadButton SHOULDER_RIGHT = new GamepadButton(); //R1
|
||||
public GamepadButton SHOULDER_LEFT = new GamepadButton(); //L1
|
||||
|
||||
public int[] DIRECTION_FORWARD;
|
||||
public int[] DIRECTION_BACKWARD;
|
||||
public int[] DIRECTION_RIGHT;
|
||||
public int[] DIRECTION_LEFT;
|
||||
|
||||
public GamepadButton THUMBSTICK_RIGHT = new GamepadButton(); //R3
|
||||
public GamepadButton THUMBSTICK_LEFT = new GamepadButton(); //L3
|
||||
|
||||
public GamepadButton DPAD_UP = new GamepadButton();
|
||||
public GamepadButton DPAD_RIGHT = new GamepadButton();
|
||||
public GamepadButton DPAD_DOWN = new GamepadButton();
|
||||
public GamepadButton DPAD_LEFT = new GamepadButton();
|
||||
|
||||
|
||||
/*
|
||||
* Sets all buttons to a not pressed state, sending an input if needed
|
||||
*/
|
||||
public void resetPressedState(){
|
||||
BUTTON_A.resetButtonState();
|
||||
BUTTON_B.resetButtonState();
|
||||
BUTTON_X.resetButtonState();
|
||||
BUTTON_Y.resetButtonState();
|
||||
|
||||
BUTTON_START.resetButtonState();
|
||||
BUTTON_SELECT.resetButtonState();
|
||||
|
||||
TRIGGER_LEFT.resetButtonState();
|
||||
TRIGGER_RIGHT.resetButtonState();
|
||||
|
||||
SHOULDER_LEFT.resetButtonState();
|
||||
SHOULDER_RIGHT.resetButtonState();
|
||||
|
||||
THUMBSTICK_LEFT.resetButtonState();
|
||||
THUMBSTICK_RIGHT.resetButtonState();
|
||||
|
||||
DPAD_UP.resetButtonState();
|
||||
DPAD_RIGHT.resetButtonState();
|
||||
DPAD_DOWN.resetButtonState();
|
||||
DPAD_LEFT.resetButtonState();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a pre-done mapping used when the mouse is grabbed by the game.
|
||||
*/
|
||||
public static GamepadMap getDefaultGameMap(){
|
||||
GamepadMap gameMap = new GamepadMap();
|
||||
|
||||
gameMap.BUTTON_A.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_SPACE};
|
||||
gameMap.BUTTON_B.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_Q};
|
||||
gameMap.BUTTON_X.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_E};
|
||||
gameMap.BUTTON_Y.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_F};
|
||||
|
||||
gameMap.DIRECTION_FORWARD = new int[]{LWJGLGLFWKeycode.GLFW_KEY_W};
|
||||
gameMap.DIRECTION_BACKWARD = new int[]{LWJGLGLFWKeycode.GLFW_KEY_S};
|
||||
gameMap.DIRECTION_RIGHT = new int[]{LWJGLGLFWKeycode.GLFW_KEY_D};
|
||||
gameMap.DIRECTION_LEFT = new int[]{LWJGLGLFWKeycode.GLFW_KEY_A};
|
||||
|
||||
gameMap.DPAD_UP.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT};
|
||||
gameMap.DPAD_DOWN.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_O}; //For mods ?
|
||||
gameMap.DPAD_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_K}; //For mods ?
|
||||
gameMap.DPAD_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_J}; //For mods ?
|
||||
|
||||
gameMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP};
|
||||
gameMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN};
|
||||
|
||||
gameMap.TRIGGER_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT};
|
||||
gameMap.TRIGGER_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT};
|
||||
|
||||
gameMap.THUMBSTICK_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL};
|
||||
gameMap.THUMBSTICK_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT};
|
||||
gameMap.THUMBSTICK_RIGHT.isToggleable = true;
|
||||
|
||||
gameMap.BUTTON_START.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
|
||||
gameMap.BUTTON_SELECT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_TAB};
|
||||
|
||||
return gameMap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a pre-done mapping used when the mouse is NOT grabbed by the game.
|
||||
*/
|
||||
public static GamepadMap getDefaultMenuMap(){
|
||||
GamepadMap menuMap = new GamepadMap();
|
||||
|
||||
menuMap.BUTTON_A.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT};
|
||||
menuMap.BUTTON_B.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
|
||||
menuMap.BUTTON_X.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT};
|
||||
menuMap.BUTTON_Y.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT, LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT}; //Oops, doesn't work since left shift isn't properly applied.
|
||||
|
||||
menuMap.DIRECTION_FORWARD = new int[]{GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP};
|
||||
menuMap.DIRECTION_BACKWARD = new int[]{GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN};
|
||||
menuMap.DIRECTION_RIGHT = new int[]{};
|
||||
menuMap.DIRECTION_LEFT = new int[]{};
|
||||
|
||||
menuMap.DPAD_UP.keycodes = new int[]{};
|
||||
menuMap.DPAD_DOWN.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_O}; //For mods ?
|
||||
menuMap.DPAD_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_K}; //For mods ?
|
||||
menuMap.DPAD_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_J}; //For mods ?
|
||||
|
||||
menuMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP};
|
||||
menuMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN};
|
||||
|
||||
menuMap.TRIGGER_LEFT.keycodes = new int[]{};
|
||||
menuMap.TRIGGER_RIGHT.keycodes = new int[]{};
|
||||
|
||||
menuMap.THUMBSTICK_LEFT.keycodes = new int[]{};
|
||||
menuMap.THUMBSTICK_RIGHT.keycodes = new int[]{};
|
||||
|
||||
menuMap.BUTTON_START.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
|
||||
menuMap.BUTTON_SELECT.keycodes = new int[]{};
|
||||
|
||||
return menuMap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns all GamepadButtons, does not include directional keys
|
||||
*/
|
||||
public GamepadButton[] getButtons(){
|
||||
return new GamepadButton[]{ BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y,
|
||||
BUTTON_SELECT, BUTTON_START,
|
||||
TRIGGER_LEFT, TRIGGER_RIGHT,
|
||||
SHOULDER_LEFT, SHOULDER_RIGHT,
|
||||
THUMBSTICK_LEFT, THUMBSTICK_RIGHT,
|
||||
DPAD_UP, DPAD_RIGHT, DPAD_DOWN, DPAD_LEFT};
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns an pre-initialized GamepadMap with only empty keycodes
|
||||
*/
|
||||
public static GamepadMap getEmptyMap(){
|
||||
GamepadMap emptyMap = new GamepadMap();
|
||||
for(GamepadButton button : emptyMap.getButtons())
|
||||
button.keycodes = new int[]{};
|
||||
|
||||
emptyMap.DIRECTION_LEFT = new int[]{};
|
||||
emptyMap.DIRECTION_FORWARD = new int[]{};
|
||||
emptyMap.DIRECTION_RIGHT = new int[]{};
|
||||
emptyMap.DIRECTION_BACKWARD = new int[]{};
|
||||
|
||||
return emptyMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This class has been splited from android/widget/Editor$HandleView.java
|
||||
*/
|
||||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.content.*;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.view.*;
|
||||
import android.view.ViewGroup.*;
|
||||
import android.widget.*;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
import android.view.View.OnClickListener;
|
||||
import net.kdt.pojavlaunch.customcontrols.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
|
||||
|
||||
import androidx.appcompat.app.*;
|
||||
|
||||
import com.rarepebble.colorpicker.ColorPickerView;
|
||||
|
||||
public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
|
||||
|
||||
private TextView mEditTextView;
|
||||
private TextView mDeleteTextView;
|
||||
private TextView mCloneTextView;
|
||||
|
||||
private final ControlButton editedButton;
|
||||
|
||||
public ActionPopupWindow(HandleView handleView, ControlButton button){
|
||||
super(handleView);
|
||||
this.editedButton = button;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createPopupWindow() {
|
||||
mPopupWindow = new PopupWindow(mHandleView.getContext(), null, android.R.attr.textSelectHandleWindowStyle);
|
||||
mPopupWindow.setClippingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initContentView() {
|
||||
LinearLayout linearLayout = new LinearLayout(mHandleView.getContext());
|
||||
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
mContentView = linearLayout;
|
||||
mContentView.setBackgroundResource(R.drawable.control_side_action_window);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) mHandleView.getContext().
|
||||
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
LayoutParams wrapContent = new LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
mEditTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
|
||||
mEditTextView.setLayoutParams(wrapContent);
|
||||
mContentView.addView(mEditTextView);
|
||||
mEditTextView.setText(R.string.global_edit);
|
||||
mEditTextView.setOnClickListener(this);
|
||||
|
||||
mDeleteTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
|
||||
mDeleteTextView.setLayoutParams(wrapContent);
|
||||
mContentView.addView(mDeleteTextView);
|
||||
mDeleteTextView.setText(R.string.global_remove);
|
||||
mDeleteTextView.setOnClickListener(this);
|
||||
|
||||
mCloneTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
|
||||
mCloneTextView.setLayoutParams(wrapContent);
|
||||
mContentView.addView(mCloneTextView);
|
||||
mCloneTextView.setText(R.string.global_clone);
|
||||
mCloneTextView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(final View view) {
|
||||
if (view == mEditTextView) {
|
||||
|
||||
if(editedButton instanceof ControlSubButton){
|
||||
new EditControlSubButtonPopup((ControlSubButton) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
if(editedButton instanceof ControlDrawer){
|
||||
new EditControlDrawerPopup((ControlDrawer) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
if(editedButton instanceof ControlButton){
|
||||
new EditControlButtonPopup((ControlButton) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
} else if (view == mDeleteTextView) {
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(view.getContext());
|
||||
alertBuilder.setCancelable(false);
|
||||
alertBuilder.setMessage(view.getContext().getString(R.string.customctrl_remove, mHandleView.mView.getText()) + "?");
|
||||
|
||||
alertBuilder.setPositiveButton(R.string.global_remove, (p1, p2) -> {
|
||||
ControlLayout layout = ((ControlLayout) mHandleView.mView.getParent());
|
||||
|
||||
if(editedButton instanceof ControlSubButton){
|
||||
layout.removeControlSubButton((ControlSubButton) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
if(editedButton instanceof ControlDrawer){
|
||||
layout.removeControlDrawer((ControlDrawer) editedButton);
|
||||
return;
|
||||
}
|
||||
|
||||
if(editedButton instanceof ControlButton){
|
||||
layout.removeControlButton((ControlButton) editedButton);
|
||||
}
|
||||
|
||||
layout.removeControlButton(mHandleView.mView);
|
||||
});
|
||||
alertBuilder.setNegativeButton(android.R.string.cancel, null);
|
||||
alertBuilder.show();
|
||||
}else if(view == mCloneTextView) {
|
||||
if(editedButton instanceof ControlDrawer){
|
||||
ControlDrawerData cloneData = new ControlDrawerData(((ControlDrawer)editedButton).getDrawerData());
|
||||
cloneData.properties.dynamicX = "0.5 * ${screen_width}";
|
||||
cloneData.properties.dynamicY = "0.5 * ${screen_height}";
|
||||
((ControlLayout) mHandleView.mView.getParent()).addDrawer(cloneData);
|
||||
}else if(editedButton instanceof ControlSubButton){
|
||||
ControlData cloneData = new ControlData(editedButton.getProperties());
|
||||
cloneData.dynamicX = "0.5 * ${screen_width}";
|
||||
cloneData.dynamicY = "0.5 * ${screen_height}";
|
||||
((ControlLayout) mHandleView.mView.getParent()).addSubButton(((ControlSubButton) editedButton).parentDrawer, cloneData);
|
||||
}else{
|
||||
ControlData cloneData = new ControlData(editedButton.getProperties());
|
||||
cloneData.dynamicX = "0.5 * ${screen_width}";
|
||||
cloneData.dynamicY = "0.5 * ${screen_height}";
|
||||
((ControlLayout) mHandleView.mView.getParent()).addControlButton(cloneData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTextOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVerticalLocalPosition(int line) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int clipVertically(int positionY) {
|
||||
return positionY;
|
||||
}
|
||||
|
||||
|
||||
public static void showColorPicker(Context ctx,String title, boolean showAlpha, ImageView v){
|
||||
int startColor = ((ColorDrawable)v.getDrawable()).getColor();
|
||||
|
||||
ColorPickerView picker = new ColorPickerView(ctx);
|
||||
picker.setColor(startColor);
|
||||
picker.showAlpha(showAlpha);
|
||||
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(ctx);
|
||||
dialog.setTitle(title);
|
||||
dialog.setView(picker);
|
||||
dialog.setNegativeButton(android.R.string.cancel, null);
|
||||
dialog.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> v.setImageDrawable(new ColorDrawable(picker.getColor())));
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public static void setPercentageText(TextView textView, int progress){
|
||||
textView.setText(progress + " %");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,328 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import net.kdt.pojavlaunch.EfficientAndroidLWJGLKeycode;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
|
||||
import top.defaults.checkerboarddrawable.CheckerboardDrawable;
|
||||
|
||||
import static net.kdt.pojavlaunch.customcontrols.handleview.ActionPopupWindow.setPercentageText;
|
||||
|
||||
public class EditControlButtonPopup {
|
||||
|
||||
protected AlertDialog dialog;
|
||||
protected View v;
|
||||
protected AlertDialog.Builder builder;
|
||||
|
||||
protected EditText editName;
|
||||
protected Spinner[] spinnersKeycode;
|
||||
|
||||
protected CheckBox checkToggle;
|
||||
protected CheckBox checkPassThrough;
|
||||
protected CheckBox checkBoxSwipeable;
|
||||
protected CheckBox checkDynamicPosition;
|
||||
|
||||
protected EditText editWidth;
|
||||
protected EditText editHeight;
|
||||
protected EditText editDynamicX;
|
||||
protected EditText editDynamicY;
|
||||
|
||||
protected SeekBar seekBarOpacity;
|
||||
protected SeekBar seekBarCornerRadius;
|
||||
protected SeekBar seekBarStrokeWidth;
|
||||
|
||||
protected ImageButton buttonBackgroundColor;
|
||||
protected ImageButton buttonStrokeColor;
|
||||
|
||||
protected TextView textOpacity;
|
||||
protected TextView textCornerRadius;
|
||||
protected TextView textStrokeWidth;
|
||||
protected TextView textStrokeColor;
|
||||
|
||||
protected final ControlButton button;
|
||||
protected final ControlData properties;
|
||||
|
||||
protected ArrayAdapter<String> adapter;
|
||||
protected String[] specialArr;
|
||||
|
||||
|
||||
public EditControlButtonPopup(ControlButton button){
|
||||
this.button = button;
|
||||
this.properties = button.getProperties();
|
||||
|
||||
initializeEditDialog(button.getContext());
|
||||
|
||||
//Create the finalized dialog
|
||||
dialog = builder.create();
|
||||
dialog.setOnShowListener(dialogInterface -> setEditDialogValues());
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
protected void initializeEditDialog(Context ctx){
|
||||
//Create the editing dialog
|
||||
LayoutInflater layoutInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
v = layoutInflater.inflate(R.layout.control_button_setting,null);
|
||||
|
||||
builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(ctx.getResources().getString(R.string.customctrl_edit, properties.name));
|
||||
builder.setView(v);
|
||||
|
||||
//Linking a lot of stuff
|
||||
editName = v.findViewById(R.id.editName_editText);
|
||||
|
||||
spinnersKeycode = new Spinner[]{
|
||||
v.findViewById(R.id.editMapping_spinner_1),
|
||||
v.findViewById(R.id.editMapping_spinner_2),
|
||||
v.findViewById(R.id.editMapping_spinner_3),
|
||||
v.findViewById(R.id.editMapping_spinner_4)
|
||||
};
|
||||
|
||||
checkToggle = v.findViewById(R.id.checkboxToggle);
|
||||
checkPassThrough = v.findViewById(R.id.checkboxPassThrough);
|
||||
checkBoxSwipeable = v.findViewById(R.id.checkboxSwipeable);
|
||||
|
||||
editWidth = v.findViewById(R.id.editSize_editTextX);
|
||||
editHeight = v.findViewById(R.id.editSize_editTextY);
|
||||
|
||||
editDynamicX = v.findViewById(R.id.editDynamicPositionX_editText);
|
||||
editDynamicY = v.findViewById(R.id.editDynamicPositionY_editText);
|
||||
|
||||
seekBarOpacity = v.findViewById(R.id.editButtonOpacity_seekbar);
|
||||
seekBarCornerRadius = v.findViewById(R.id.editCornerRadius_seekbar);
|
||||
seekBarStrokeWidth = v.findViewById(R.id.editStrokeWidth_seekbar);
|
||||
|
||||
SeekBar.OnSeekBarChangeListener changeListener = new SeekBar.OnSeekBarChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
|
||||
if(seekBar.equals(seekBarCornerRadius)) {
|
||||
setPercentageText(textCornerRadius, i);
|
||||
return;
|
||||
}
|
||||
if(seekBar.equals(seekBarOpacity)) {
|
||||
setPercentageText(textOpacity, i);
|
||||
return;
|
||||
}
|
||||
if(seekBar.equals(seekBarStrokeWidth)) {
|
||||
setPercentageText(textStrokeWidth, i);
|
||||
textStrokeColor.setVisibility(i == 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {/*STUB*/}
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {/*STUB*/}
|
||||
};
|
||||
|
||||
//Add listeners, too bad I don't need all the methods
|
||||
seekBarOpacity.setOnSeekBarChangeListener(changeListener);
|
||||
seekBarCornerRadius.setOnSeekBarChangeListener(changeListener);
|
||||
seekBarStrokeWidth.setOnSeekBarChangeListener(changeListener);
|
||||
|
||||
buttonBackgroundColor = v.findViewById(R.id.editBackgroundColor_imageButton);
|
||||
buttonStrokeColor = v.findViewById(R.id.editStrokeColor_imageButton);
|
||||
|
||||
textOpacity = v.findViewById(R.id.editButtonOpacity_textView_percent);
|
||||
textCornerRadius = v.findViewById(R.id.editCornerRadius_textView_percent);
|
||||
textStrokeWidth = v.findViewById(R.id.editStrokeWidth_textView_percent);
|
||||
textStrokeColor = v.findViewById(R.id.editStrokeColor_textView);
|
||||
|
||||
checkDynamicPosition = v.findViewById(R.id.checkboxDynamicPosition);
|
||||
checkDynamicPosition.setOnCheckedChangeListener((btn, checked) -> {
|
||||
editDynamicX.setEnabled(checked);
|
||||
editDynamicY.setEnabled(checked);
|
||||
});
|
||||
|
||||
|
||||
//Initialize adapter for keycodes
|
||||
adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
|
||||
String[] oldSpecialArr = ControlData.buildSpecialButtonArray();
|
||||
specialArr = new String[oldSpecialArr.length];
|
||||
for (int i = 0; i < specialArr.length; i++) {
|
||||
specialArr[i] = "SPECIAL_" + oldSpecialArr[specialArr.length - i - 1];
|
||||
}
|
||||
adapter.addAll(specialArr);
|
||||
adapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
|
||||
|
||||
for (Spinner spinner : spinnersKeycode) {
|
||||
spinner.setAdapter(adapter);
|
||||
}
|
||||
|
||||
//Set color imageButton behavior
|
||||
buttonBackgroundColor.setOnClickListener(view -> ActionPopupWindow.showColorPicker(ctx, "Edit background color", true, buttonBackgroundColor));
|
||||
buttonStrokeColor.setOnClickListener(view -> ActionPopupWindow.showColorPicker(ctx, "Edit stroke color", false, buttonStrokeColor));
|
||||
|
||||
|
||||
//Set dialog buttons behavior
|
||||
setupDialogButtons();
|
||||
|
||||
hideUselessViews();
|
||||
|
||||
defineDynamicCheckChange();
|
||||
|
||||
setupCheckerboards();
|
||||
}
|
||||
|
||||
protected void setupDialogButtons(){
|
||||
//Set dialog buttons behavior
|
||||
builder.setPositiveButton(android.R.string.ok, (dialogInterface1, i) -> {
|
||||
if(!hasPropertiesErrors(dialog.getContext())){
|
||||
saveProperties();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
protected void hideUselessViews(){
|
||||
(v.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
|
||||
|
||||
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
|
||||
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
|
||||
editDynamicX.setVisibility(View.GONE);
|
||||
editDynamicY.setVisibility(View.GONE);
|
||||
|
||||
//Hide the color choice if the width is 0.
|
||||
textStrokeColor.setVisibility(properties.strokeWidth == 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
protected void defineDynamicCheckChange(){
|
||||
checkDynamicPosition.setOnCheckedChangeListener((compoundButton, b) -> {
|
||||
int visibility = b ? View.VISIBLE : View.GONE;
|
||||
|
||||
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(visibility);
|
||||
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(visibility);
|
||||
editDynamicX.setVisibility(visibility);
|
||||
editDynamicY.setVisibility(visibility);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void setupCheckerboards(){
|
||||
CheckerboardDrawable drawable = new CheckerboardDrawable.Builder()
|
||||
.colorEven(Color.LTGRAY)
|
||||
.colorOdd(Color.WHITE)
|
||||
.size((int) Tools.dpToPx(20))
|
||||
.build();
|
||||
|
||||
buttonBackgroundColor.setBackground(drawable);
|
||||
buttonStrokeColor.setBackground(drawable);
|
||||
}
|
||||
|
||||
protected void setEditDialogValues(){
|
||||
|
||||
editName.setText(properties.name);
|
||||
|
||||
checkToggle.setChecked(properties.isToggle);
|
||||
checkPassThrough.setChecked(properties.passThruEnabled);
|
||||
checkBoxSwipeable.setChecked(properties.isSwipeable);
|
||||
|
||||
editWidth.setText(Float.toString(properties.getWidth()));
|
||||
editHeight.setText(Float.toString(properties.getHeight()));
|
||||
|
||||
editDynamicX.setEnabled(properties.isDynamicBtn);
|
||||
editDynamicY.setEnabled(properties.isDynamicBtn);
|
||||
editDynamicX.setText(properties.dynamicX);
|
||||
|
||||
editDynamicY.setText(properties.dynamicY);
|
||||
|
||||
seekBarOpacity.setProgress((int) (properties.opacity*100));
|
||||
seekBarStrokeWidth.setProgress(properties.strokeWidth);
|
||||
seekBarCornerRadius.setProgress((int)properties.cornerRadius);
|
||||
|
||||
buttonBackgroundColor.setImageDrawable(new ColorDrawable(properties.bgColor));
|
||||
buttonStrokeColor.setImageDrawable(new ColorDrawable(properties.strokeColor));
|
||||
|
||||
setPercentageText(textCornerRadius,seekBarCornerRadius.getProgress());
|
||||
setPercentageText(textOpacity,seekBarOpacity.getProgress());
|
||||
setPercentageText(textStrokeWidth,seekBarStrokeWidth.getProgress());
|
||||
|
||||
checkDynamicPosition.setChecked(properties.isDynamicBtn);
|
||||
|
||||
for(int i=0; i< properties.keycodes.length; i++){
|
||||
if (properties.keycodes[i] < 0) {
|
||||
spinnersKeycode[i].setSelection(properties.keycodes[i] + specialArr.length);
|
||||
} else {
|
||||
spinnersKeycode[i].setSelection(EfficientAndroidLWJGLKeycode.getIndexByValue(properties.keycodes[i]) + specialArr.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected boolean hasPropertiesErrors(Context ctx){
|
||||
if (editName.getText().toString().isEmpty()) {
|
||||
editName.setError(ctx.getResources().getString(R.string.global_error_field_empty));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (properties.isDynamicBtn) {
|
||||
|
||||
int errorAt = 0;
|
||||
try {
|
||||
properties.insertDynamicPos(editDynamicX.getText().toString());
|
||||
errorAt = 1;
|
||||
properties.insertDynamicPos(editDynamicY.getText().toString());
|
||||
} catch (Throwable th) {
|
||||
(errorAt == 0 ? editDynamicX : editDynamicY).setError(th.getMessage());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void saveProperties(){
|
||||
//This method assumes there are no error.
|
||||
properties.name = editName.getText().toString();
|
||||
|
||||
//Keycodes
|
||||
for(int i=0; i<spinnersKeycode.length; ++i){
|
||||
if (spinnersKeycode[i].getSelectedItemPosition() < specialArr.length) {
|
||||
properties.keycodes[i] = spinnersKeycode[i].getSelectedItemPosition() - specialArr.length;
|
||||
} else {
|
||||
properties.keycodes[i] = EfficientAndroidLWJGLKeycode.getValueByIndex(spinnersKeycode[i].getSelectedItemPosition() - specialArr.length);
|
||||
}
|
||||
}
|
||||
|
||||
properties.opacity = seekBarOpacity.getProgress()/100f;
|
||||
properties.strokeWidth = seekBarStrokeWidth.getProgress();
|
||||
properties.cornerRadius = seekBarCornerRadius.getProgress();
|
||||
|
||||
properties.bgColor = ((ColorDrawable)buttonBackgroundColor.getDrawable()).getColor();
|
||||
properties.strokeColor = ((ColorDrawable) buttonStrokeColor.getDrawable()).getColor();
|
||||
|
||||
properties.isToggle = checkToggle.isChecked();
|
||||
properties.passThruEnabled = checkPassThrough.isChecked();
|
||||
properties.isSwipeable = checkBoxSwipeable.isChecked();
|
||||
|
||||
properties.setWidth(Float.parseFloat(editWidth.getText().toString()));
|
||||
properties.setHeight(Float.parseFloat(editHeight.getText().toString()));
|
||||
|
||||
properties.isDynamicBtn = checkDynamicPosition.isChecked();
|
||||
if(!editDynamicX.getText().toString().isEmpty()) properties.dynamicX = editDynamicX.getText().toString();
|
||||
if(!editDynamicY.getText().toString().isEmpty()) properties.dynamicY = editDynamicY.getText().toString();
|
||||
|
||||
button.updateProperties();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlData;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
|
||||
|
||||
public class EditControlDrawerPopup extends EditControlButtonPopup{
|
||||
private Spinner spinnerOrientation;
|
||||
|
||||
|
||||
private ControlDrawer drawer;
|
||||
private ControlDrawerData drawerData;
|
||||
|
||||
public EditControlDrawerPopup(ControlDrawer editedButton) {
|
||||
super(editedButton);
|
||||
drawer = editedButton;
|
||||
drawerData = editedButton.getDrawerData();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void hideUselessViews() {
|
||||
(v.findViewById(R.id.editMapping_textView)).setVisibility(View.GONE);
|
||||
checkPassThrough.setVisibility(View.GONE);
|
||||
checkToggle.setVisibility(View.GONE);
|
||||
checkBoxSwipeable.setVisibility(View.GONE);
|
||||
|
||||
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
|
||||
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
|
||||
editDynamicX.setVisibility(View.GONE);
|
||||
editDynamicY.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeEditDialog(Context ctx) {
|
||||
super.initializeEditDialog(ctx);
|
||||
|
||||
spinnerOrientation = v.findViewById(R.id.editOrientation_spinner);
|
||||
|
||||
ArrayAdapter<ControlDrawerData.Orientation> adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
|
||||
adapter.addAll(ControlDrawerData.getOrientations());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
|
||||
|
||||
spinnerOrientation.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setEditDialogValues() {
|
||||
super.setEditDialogValues();
|
||||
|
||||
spinnerOrientation.setSelection(ControlDrawerData.orientationToInt(drawerData.orientation));
|
||||
|
||||
|
||||
//Using the dialog to replace the button behavior allows us not to dismiss the window
|
||||
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
ControlLayout layout = (ControlLayout) drawer.getParent();
|
||||
ControlData controlData = new ControlData(drawerData.properties);
|
||||
controlData.name = "new";
|
||||
layout.addSubButton(drawer, controlData);
|
||||
|
||||
Context ctx = dialog.getContext();
|
||||
Toast.makeText(ctx, ctx.getString(R.string.customctrl_add_subbutton_message,
|
||||
drawer.getDrawerData().buttonProperties.size()), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupDialogButtons() {
|
||||
super.setupDialogButtons();
|
||||
|
||||
builder.setNeutralButton(v.getResources().getString(R.string.customctrl_addsubbutton), null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveProperties() {
|
||||
drawerData.orientation = ControlDrawerData.intToOrientation(spinnerOrientation.getSelectedItemPosition());
|
||||
super.saveProperties();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
|
||||
public class EditControlSubButtonPopup extends EditControlButtonPopup{
|
||||
|
||||
|
||||
public EditControlSubButtonPopup(ControlButton button){
|
||||
super(button);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hideUselessViews() {
|
||||
(v.findViewById(R.id.editSize_textView)).setVisibility(View.GONE);
|
||||
(v.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
|
||||
|
||||
checkDynamicPosition.setVisibility(View.GONE);
|
||||
|
||||
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
|
||||
editDynamicX.setVisibility(View.GONE);
|
||||
|
||||
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
|
||||
editDynamicY.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,405 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This class has been splited from android/widget/Editor$HandleView.java
|
||||
*/
|
||||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.graphics.*;
|
||||
import android.graphics.drawable.*;
|
||||
import android.os.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
|
||||
|
||||
public abstract class HandleView extends View implements ViewPositionListener, View.OnLongClickListener
|
||||
{
|
||||
protected Drawable mDrawable;
|
||||
protected Drawable mDrawableLtr;
|
||||
protected Drawable mDrawableRtl;
|
||||
private final PopupWindow mContainer;
|
||||
// Position with respect to the parent TextView
|
||||
private int mPositionX, mPositionY;
|
||||
private boolean mIsDragging;
|
||||
// Offset from touch position to mPosition
|
||||
private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
|
||||
protected int mHotspotX;
|
||||
protected int mHorizontalGravity;
|
||||
// Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
|
||||
private float mTouchOffsetY;
|
||||
// Where the touch position should be on the handle to ensure a maximum cursor visibility
|
||||
private float mIdealVerticalOffset;
|
||||
// Parent's (TextView) previous position in window
|
||||
private int mLastParentX, mLastParentY;
|
||||
// Transient action popup window for Paste and Replace actions
|
||||
protected ActionPopupWindow mActionPopupWindow;
|
||||
// Previous text character offset
|
||||
private int mPreviousOffset = -1;
|
||||
// Previous text character offset
|
||||
private boolean mPositionHasChanged = true;
|
||||
// Used to delay the appearance of the action popup window
|
||||
private Runnable mActionPopupShower;
|
||||
// Minimum touch target size for handles
|
||||
private int mMinSize;
|
||||
protected ControlButton mView;
|
||||
|
||||
// MOD: Addition. Save old size of parent
|
||||
private int mDownWidth, mDownHeight;
|
||||
// int mWindowPosX, mWindowPosY;
|
||||
|
||||
private PositionListener mPositionListener;
|
||||
public PositionListener getPositionListener() {
|
||||
if (mPositionListener == null) {
|
||||
mPositionListener = new PositionListener(mView);
|
||||
}
|
||||
return mPositionListener;
|
||||
}
|
||||
|
||||
public HandleView(ControlButton view) {
|
||||
super(view.getContext());
|
||||
|
||||
mView = view;
|
||||
|
||||
mDownWidth = view.getLayoutParams().width;
|
||||
mDownHeight = view.getLayoutParams().height;
|
||||
|
||||
mContainer = new PopupWindow(view.getContext(), null, android.R.attr.textSelectHandleWindowStyle);
|
||||
mContainer.setSplitTouchEnabled(true);
|
||||
mContainer.setClippingEnabled(false);
|
||||
mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
|
||||
mContainer.setContentView(this);
|
||||
|
||||
mDrawableLtr = view.getContext().getDrawable(R.drawable.text_select_handle_left_material);
|
||||
mDrawableRtl = view.getContext().getDrawable(R.drawable.text_select_handle_right_material);
|
||||
mMinSize = view.getContext().getResources().getDimensionPixelSize(R.dimen.text_handle_min_size);
|
||||
|
||||
setOnLongClickListener(this);
|
||||
|
||||
updateDrawable();
|
||||
|
||||
final int handleHeight = getPreferredHeight();
|
||||
mTouchOffsetY = -0.3f * handleHeight;
|
||||
mIdealVerticalOffset = 0.7f * handleHeight;
|
||||
}
|
||||
|
||||
|
||||
protected void updateDrawable() {
|
||||
// final int offset = getCurrentCursorOffset();
|
||||
final boolean isRtlCharAtOffset = true; // mView.getLayout().isRtlCharAt(offset);
|
||||
mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
|
||||
mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
|
||||
mHorizontalGravity = getHorizontalGravity(isRtlCharAtOffset);
|
||||
}
|
||||
|
||||
protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
|
||||
protected abstract int getHorizontalGravity(boolean isRtlRun);
|
||||
|
||||
// Touch-up filter: number of previous positions remembered
|
||||
private static final int HISTORY_SIZE = 5;
|
||||
private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
|
||||
private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
|
||||
private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
|
||||
private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
|
||||
private int mPreviousOffsetIndex = 0;
|
||||
private int mNumberPreviousOffsets = 0;
|
||||
|
||||
private void startTouchUpFilter(int offset) {
|
||||
mNumberPreviousOffsets = 0;
|
||||
addPositionToTouchUpFilter(offset);
|
||||
}
|
||||
|
||||
private void addPositionToTouchUpFilter(int offset) {
|
||||
mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
|
||||
mPreviousOffsets[mPreviousOffsetIndex] = offset;
|
||||
mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
|
||||
mNumberPreviousOffsets++;
|
||||
}
|
||||
|
||||
private void filterOnTouchUp() {
|
||||
final long now = SystemClock.uptimeMillis();
|
||||
int i = 0;
|
||||
int index = mPreviousOffsetIndex;
|
||||
final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
|
||||
while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
|
||||
i++;
|
||||
index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
|
||||
}
|
||||
|
||||
if (i > 0 && i < iMax &&
|
||||
(now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
|
||||
positionAtCursorOffset(mPreviousOffsets[index], false);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean offsetHasBeenChanged() {
|
||||
return mNumberPreviousOffsets > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
setMeasuredDimension(getPreferredWidth(), getPreferredHeight());
|
||||
}
|
||||
|
||||
private int getPreferredWidth() {
|
||||
return Math.max(mDrawable.getIntrinsicWidth(), mMinSize);
|
||||
}
|
||||
|
||||
private int getPreferredHeight() {
|
||||
return Math.max(mDrawable.getIntrinsicHeight(), mMinSize);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (isShowing()) return;
|
||||
|
||||
getPositionListener().addSubscriber(this, true /* local position may change */);
|
||||
|
||||
// Make sure the offset is always considered new, even when focusing at same position
|
||||
mPreviousOffset = -1;
|
||||
positionAtCursorOffset(getCurrentCursorOffset(), false);
|
||||
|
||||
hideActionPopupWindow();
|
||||
}
|
||||
|
||||
protected void dismiss() {
|
||||
mIsDragging = false;
|
||||
mContainer.dismiss();
|
||||
onDetached();
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
dismiss();
|
||||
|
||||
getPositionListener().removeSubscriber(this);
|
||||
}
|
||||
|
||||
void showActionPopupWindow(int delay, ControlButton button) {
|
||||
if (mActionPopupWindow == null) {
|
||||
mActionPopupWindow = new ActionPopupWindow(this, button);
|
||||
}
|
||||
if (mActionPopupShower == null) {
|
||||
mActionPopupShower = new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
mActionPopupWindow.show();
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
mView.removeCallbacks(mActionPopupShower);
|
||||
}
|
||||
mView.postDelayed(mActionPopupShower, delay);
|
||||
}
|
||||
|
||||
protected void hideActionPopupWindow() {
|
||||
if (mActionPopupShower != null) {
|
||||
mView.removeCallbacks(mActionPopupShower);
|
||||
}
|
||||
if (mActionPopupWindow != null) {
|
||||
mActionPopupWindow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mContainer.isShowing();
|
||||
}
|
||||
|
||||
private boolean isVisible() {
|
||||
// Always show a dragging handle.
|
||||
if (mIsDragging) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return mView.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
public abstract int getCurrentCursorOffset();
|
||||
|
||||
protected abstract void updateSelection(int offset);
|
||||
|
||||
public abstract void updatePosition(float x, float y);
|
||||
|
||||
protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
|
||||
mPositionX = mView.getWidth();
|
||||
mPositionY = mView.getHeight();
|
||||
|
||||
mPositionHasChanged = true;
|
||||
}
|
||||
|
||||
public void updatePosition(int parentPositionX, int parentPositionY,
|
||||
boolean parentPositionChanged, boolean parentScrolled) {
|
||||
positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
|
||||
if (parentPositionChanged || mPositionHasChanged) {
|
||||
if (mIsDragging) {
|
||||
// Update touchToWindow offset in case of parent scrolling while dragging
|
||||
if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
|
||||
mTouchToWindowOffsetX += parentPositionX - mLastParentX;
|
||||
mTouchToWindowOffsetY += parentPositionY - mLastParentY;
|
||||
mLastParentX = parentPositionX;
|
||||
mLastParentY = parentPositionY;
|
||||
}
|
||||
|
||||
onHandleMoved();
|
||||
}
|
||||
|
||||
if (isVisible()) {
|
||||
final int positionX = parentPositionX + mPositionX;
|
||||
final int positionY = parentPositionY + mPositionY;
|
||||
/*
|
||||
mWindowPosX = positionX;
|
||||
mWindowPosY = positionY;
|
||||
*/
|
||||
if (isShowing()) {
|
||||
mContainer.update(positionX, positionY, -1, -1);
|
||||
} else {
|
||||
mContainer.showAtLocation(mView, Gravity.NO_GRAVITY,
|
||||
positionX, positionY);
|
||||
}
|
||||
} else {
|
||||
if (isShowing()) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
mPositionHasChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas c) {
|
||||
final int drawWidth = mDrawable.getIntrinsicWidth();
|
||||
final int left = getHorizontalOffset();
|
||||
|
||||
mDrawable.setBounds(left, 0, left + drawWidth, mDrawable.getIntrinsicHeight());
|
||||
mDrawable.draw(c);
|
||||
}
|
||||
|
||||
private int getHorizontalOffset() {
|
||||
final int width = getPreferredWidth();
|
||||
final int drawWidth = mDrawable.getIntrinsicWidth();
|
||||
final int left;
|
||||
switch (mHorizontalGravity) {
|
||||
case Gravity.LEFT:
|
||||
left = 0;
|
||||
break;
|
||||
default:
|
||||
case Gravity.CENTER:
|
||||
left = (width - drawWidth) / 2;
|
||||
break;
|
||||
case Gravity.RIGHT:
|
||||
left = width - drawWidth;
|
||||
break;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
protected int getCursorOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Addition
|
||||
private float mDownX, mDownY;
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
ViewGroup.LayoutParams params = mView.getLayoutParams();
|
||||
|
||||
switch (ev.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
startTouchUpFilter(getCurrentCursorOffset());
|
||||
mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
|
||||
mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
|
||||
|
||||
final PositionListener positionListener = getPositionListener();
|
||||
mLastParentX = positionListener.getPositionX();
|
||||
mLastParentY = positionListener.getPositionY();
|
||||
mIsDragging = true;
|
||||
|
||||
// MOD: Addition
|
||||
mDownX = ev.getRawX();
|
||||
mDownY = ev.getRawY();
|
||||
mDownWidth = params.width;
|
||||
mDownHeight = params.height;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final float rawX = ev.getRawX();
|
||||
final float rawY = ev.getRawY();
|
||||
|
||||
// Vertical hysteresis: vertical down movement tends to snap to ideal offset
|
||||
final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
|
||||
final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
|
||||
float newVerticalOffset;
|
||||
if (previousVerticalOffset < mIdealVerticalOffset) {
|
||||
newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
|
||||
newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
|
||||
} else {
|
||||
newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
|
||||
newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
|
||||
}
|
||||
mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
|
||||
|
||||
final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
|
||||
final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
|
||||
|
||||
int newWidth = (int) (mDownWidth + (rawX - mDownX));
|
||||
int newHeight = (int) (mDownHeight + (rawY - mDownY));
|
||||
|
||||
// mDownX = rawX;
|
||||
// mDownY = rawY;
|
||||
|
||||
params.width = Math.max(50, newWidth);
|
||||
params.height = Math.max(50, newHeight);
|
||||
|
||||
mView.setLayoutParams(params);
|
||||
|
||||
updatePosition(newPosX, newPosY);
|
||||
// break;
|
||||
return true;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
filterOnTouchUp();
|
||||
mIsDragging = false;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mIsDragging = false;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDragging() {
|
||||
return mIsDragging;
|
||||
}
|
||||
|
||||
void onHandleMoved() {
|
||||
hideActionPopupWindow();
|
||||
}
|
||||
|
||||
public void onDetached() {
|
||||
hideActionPopupWindow();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This class has been splited from android/widget/Editor$HandleView.java
|
||||
*/
|
||||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.view.ViewGroup.*;
|
||||
import android.widget.*;
|
||||
|
||||
public abstract class PinnedPopupWindow implements ViewPositionListener {
|
||||
protected PopupWindow mPopupWindow;
|
||||
protected ViewGroup mContentView;
|
||||
int mPositionX, mPositionY;
|
||||
|
||||
protected HandleView mHandleView;
|
||||
|
||||
protected abstract void createPopupWindow();
|
||||
protected abstract void initContentView();
|
||||
protected abstract int getTextOffset();
|
||||
protected abstract int getVerticalLocalPosition(int line);
|
||||
protected abstract int clipVertically(int positionY);
|
||||
|
||||
public PinnedPopupWindow(HandleView handleView) {
|
||||
mHandleView = handleView;
|
||||
|
||||
createPopupWindow();
|
||||
|
||||
mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
|
||||
mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
initContentView();
|
||||
|
||||
LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mContentView.setLayoutParams(wrapContent);
|
||||
|
||||
mPopupWindow.setContentView(mContentView);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
mHandleView.getPositionListener().addSubscriber(this, false /* offset is fixed */);
|
||||
|
||||
computeLocalPosition();
|
||||
|
||||
final PositionListener positionListener = mHandleView.getPositionListener();
|
||||
updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
|
||||
}
|
||||
|
||||
protected void measureContent() {
|
||||
final DisplayMetrics displayMetrics = mHandleView.getResources().getDisplayMetrics();
|
||||
mContentView.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
|
||||
View.MeasureSpec.AT_MOST),
|
||||
View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
|
||||
View.MeasureSpec.AT_MOST));
|
||||
}
|
||||
|
||||
/* The popup window will be horizontally centered on the getTextOffset() and vertically
|
||||
* positioned according to viewportToContentHorizontalOffset.
|
||||
*
|
||||
* This method assumes that mContentView has properly been measured from its content. */
|
||||
private void computeLocalPosition() {
|
||||
measureContent();
|
||||
|
||||
/*
|
||||
final int width = mContentView.getMeasuredWidth();
|
||||
final int offset = getTextOffset();
|
||||
mPositionX = (int) (mTextView.getLayout().getPrimaryHorizontal(offset) - width / 2.0f);
|
||||
mPositionX += mTextView.viewportToContentHorizontalOffset();
|
||||
|
||||
final int line = mTextView.getLayout().getLineForOffset(offset);
|
||||
mPositionY = getVerticalLocalPosition(line);
|
||||
mPositionY += mTextView.viewportToContentVerticalOffset();
|
||||
*/
|
||||
|
||||
/*
|
||||
mPositionX = (int) mHandleView.mView.getTranslationX();
|
||||
mPositionY = (int) mHandleView.mView.getTranslationY();
|
||||
*/
|
||||
|
||||
mPositionY = mHandleView.mView.getHeight();
|
||||
}
|
||||
|
||||
private void updatePosition(int parentPositionX, int parentPositionY) {
|
||||
int positionX = parentPositionX + mPositionX;
|
||||
int positionY = parentPositionY + mPositionY;
|
||||
|
||||
positionY = clipVertically(positionY);
|
||||
|
||||
// Horizontal clipping
|
||||
final DisplayMetrics displayMetrics = mHandleView.getResources().getDisplayMetrics();
|
||||
final int width = mContentView.getMeasuredWidth();
|
||||
positionX = Math.min(displayMetrics.widthPixels - width, positionX);
|
||||
positionX = Math.max(0, positionX);
|
||||
|
||||
if (isShowing()) {
|
||||
mPopupWindow.update(positionX, positionY, -1, -1);
|
||||
} else {
|
||||
mPopupWindow.showAtLocation(mHandleView, Gravity.NO_GRAVITY,
|
||||
positionX, positionY);
|
||||
}
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
mPopupWindow.dismiss();
|
||||
mHandleView.getPositionListener().removeSubscriber(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePosition(int parentPositionX, int parentPositionY,
|
||||
boolean parentPositionChanged, boolean parentScrolled) {
|
||||
// Either parentPositionChanged or parentScrolled is true, check if still visible
|
||||
if (isShowing()) {
|
||||
if (parentScrolled) computeLocalPosition();
|
||||
updatePosition(parentPositionX, parentPositionY);
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mPopupWindow.isShowing();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This class has been splited from android/widget/Editor$HandleView.java
|
||||
*/
|
||||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.view.*;
|
||||
|
||||
public class PositionListener implements ViewTreeObserver.OnPreDrawListener {
|
||||
// 3 handles
|
||||
// 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
|
||||
// 1 CursorAnchorInfoNotifier
|
||||
private final int MAXIMUM_NUMBER_OF_LISTENERS = 7;
|
||||
private ViewPositionListener[] mPositionListeners =
|
||||
new ViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
|
||||
private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
|
||||
private boolean mPositionHasChanged = true;
|
||||
// Absolute position of the TextView with respect to its parent window
|
||||
private int mPositionX, mPositionY;
|
||||
private int mNumberOfListeners;
|
||||
private boolean mScrollHasChanged;
|
||||
final int[] mTempCoords = new int[2];
|
||||
private View mView;
|
||||
|
||||
public PositionListener(View view) {
|
||||
mView = view;
|
||||
}
|
||||
|
||||
public void addSubscriber(ViewPositionListener positionListener, boolean canMove) {
|
||||
if (mNumberOfListeners == 0) {
|
||||
updatePosition();
|
||||
ViewTreeObserver vto = mView.getViewTreeObserver();
|
||||
vto.addOnPreDrawListener(this);
|
||||
}
|
||||
|
||||
int emptySlotIndex = -1;
|
||||
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
|
||||
ViewPositionListener listener = mPositionListeners[i];
|
||||
if (listener == positionListener) {
|
||||
return;
|
||||
} else if (emptySlotIndex < 0 && listener == null) {
|
||||
emptySlotIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
mPositionListeners[emptySlotIndex] = positionListener;
|
||||
mCanMove[emptySlotIndex] = canMove;
|
||||
mNumberOfListeners++;
|
||||
}
|
||||
|
||||
public void removeSubscriber(ViewPositionListener positionListener) {
|
||||
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
|
||||
if (mPositionListeners[i] == positionListener) {
|
||||
mPositionListeners[i] = null;
|
||||
mNumberOfListeners--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mNumberOfListeners == 0) {
|
||||
ViewTreeObserver vto = mView.getViewTreeObserver();
|
||||
vto.removeOnPreDrawListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public int getPositionX() {
|
||||
return mPositionX;
|
||||
}
|
||||
|
||||
public int getPositionY() {
|
||||
return mPositionY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
updatePosition();
|
||||
|
||||
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
|
||||
if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) {
|
||||
ViewPositionListener positionListener = mPositionListeners[i];
|
||||
if (positionListener != null) {
|
||||
positionListener.updatePosition(mPositionX, mPositionY,
|
||||
mPositionHasChanged, mScrollHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mScrollHasChanged = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updatePosition() {
|
||||
mView.getLocationInWindow(mTempCoords);
|
||||
|
||||
mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY;
|
||||
|
||||
mPositionX = mTempCoords[0];
|
||||
mPositionY = mTempCoords[1];
|
||||
}
|
||||
|
||||
public void onScrollChanged() {
|
||||
mScrollHasChanged = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This class has been splited from android/widget/Editor$HandleView.java
|
||||
*/
|
||||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
import android.graphics.drawable.*;
|
||||
import android.view.*;
|
||||
|
||||
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
|
||||
|
||||
|
||||
public class SelectionEndHandleView extends HandleView
|
||||
{
|
||||
public SelectionEndHandleView(ControlButton view) {
|
||||
super(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
|
||||
if (isRtlRun) {
|
||||
return (drawable.getIntrinsicWidth() * 3) / 4;
|
||||
} else {
|
||||
return drawable.getIntrinsicWidth() / 4;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getHorizontalGravity(boolean isRtlRun) {
|
||||
return isRtlRun ? Gravity.LEFT : Gravity.RIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentCursorOffset() {
|
||||
return 0; // mView.getSelectionEnd();
|
||||
}
|
||||
|
||||
public void show(ControlButton button){
|
||||
super.show();
|
||||
showActionPopupWindow(0, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSelection(int offset) {
|
||||
// Selection.setSelection((Spannable) mView.getText(), mView.getSelectionStart(), offset);
|
||||
updateDrawable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePosition(float x, float y) {
|
||||
// updatePosition((int) x, (int) y, false, false);
|
||||
positionAtCursorOffset(0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
//TODO stub
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This class has been splited from android/widget/Editor$HandleView.java
|
||||
*/
|
||||
package net.kdt.pojavlaunch.customcontrols.handleview;
|
||||
|
||||
public interface ViewPositionListener {
|
||||
public void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled);
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
package net.kdt.pojavlaunch.extra;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* Class providing callback across all of a program
|
||||
* to allow easy thread safe implementations of UI update without context leak
|
||||
* It is also perfectly engineered to make it unpleasant to use.
|
||||
*
|
||||
* This class uses a singleton pattern to simplify access to it
|
||||
*/
|
||||
public final class ExtraCore {
|
||||
// No unwanted instantiation
|
||||
private ExtraCore(){}
|
||||
|
||||
// Store the key-value pair
|
||||
private final Map<String, Object> valueMap = new ConcurrentHashMap<>();
|
||||
|
||||
// Store what each ExtraListener listen to
|
||||
private final Map<String, ConcurrentLinkedQueue<WeakReference<ExtraListener>>> listenerMap = new ConcurrentHashMap<>();
|
||||
|
||||
// Inner class for singleton implementation
|
||||
private static class ExtraCoreSingleton {
|
||||
private static final ExtraCore extraCore = new ExtraCore();
|
||||
}
|
||||
|
||||
// All public methods will pass through this one
|
||||
private static ExtraCore getInstance(){
|
||||
return ExtraCoreSingleton.extraCore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value associated to a key and trigger all listeners
|
||||
* @param key The key
|
||||
* @param value The value
|
||||
*/
|
||||
public static void setValue(String key, Object value){
|
||||
getInstance().valueMap.put(key, value);
|
||||
ConcurrentLinkedQueue<WeakReference<ExtraListener>> extraListenerList = getInstance().listenerMap.get(key);
|
||||
if(extraListenerList == null) return; //No listeners
|
||||
for(WeakReference<ExtraListener> listener : extraListenerList){
|
||||
if(listener.get() == null){
|
||||
extraListenerList.remove(listener);
|
||||
continue;
|
||||
}
|
||||
|
||||
//Notify the listener about a state change and remove it if asked for
|
||||
if(listener.get().onValueSet(key, value)){
|
||||
ExtraCore.removeExtraListenerFromValue(key, listener.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return The value behind the key */
|
||||
public static Object getValue(String key){
|
||||
return getInstance().valueMap.get(key);
|
||||
}
|
||||
|
||||
/** Remove the key and its value from the valueMap */
|
||||
public static void removeValue(String key){
|
||||
getInstance().valueMap.remove(key);
|
||||
}
|
||||
|
||||
/** Remove all values */
|
||||
public static void removeAllValues(){
|
||||
getInstance().valueMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Link an ExtraListener to a value
|
||||
* @param key The value key to look for
|
||||
* @param listener The ExtraListener to link
|
||||
*/
|
||||
public static void addExtraListener(String key, ExtraListener listener){
|
||||
ConcurrentLinkedQueue<WeakReference<ExtraListener>> listenerList = getInstance().listenerMap.get(key);
|
||||
// Look for new sets
|
||||
if(listenerList == null){
|
||||
listenerList = new ConcurrentLinkedQueue<>();
|
||||
getInstance().listenerMap.put(key, listenerList);
|
||||
}
|
||||
|
||||
// This is kinda naive, I should look for duplicates
|
||||
listenerList.add(new WeakReference<>(listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink an ExtraListener from a value.
|
||||
* Unlink null references found along the way
|
||||
* @param key The value key to ignore now
|
||||
* @param listener The ExtraListener to unlink
|
||||
*/
|
||||
public static void removeExtraListenerFromValue(String key, ExtraListener listener){
|
||||
ConcurrentLinkedQueue<WeakReference<ExtraListener>> listenerList = getInstance().listenerMap.get(key);
|
||||
// Look for new sets
|
||||
if(listenerList == null){
|
||||
listenerList = new ConcurrentLinkedQueue<>();
|
||||
getInstance().listenerMap.put(key, listenerList);
|
||||
}
|
||||
|
||||
// Removes all occurrences of ExtraListener and all null references
|
||||
for(WeakReference<ExtraListener> listenerWeakReference : listenerList){
|
||||
ExtraListener actualListener = listenerWeakReference.get();
|
||||
|
||||
if(actualListener == null || actualListener == listener){
|
||||
listenerList.remove(listenerWeakReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all ExtraListeners from a value
|
||||
* @param key The key to which ExtraListener are linked
|
||||
*/
|
||||
public static void removeAllExtraListenersFromValue(String key){
|
||||
ConcurrentLinkedQueue<WeakReference<ExtraListener>> listenerList = getInstance().listenerMap.get(key);
|
||||
// Look for new sets
|
||||
if(listenerList == null){
|
||||
listenerList = new ConcurrentLinkedQueue<>();
|
||||
getInstance().listenerMap.put(key, listenerList);
|
||||
}
|
||||
|
||||
listenerList.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all ExtraListeners from listening to any value
|
||||
*/
|
||||
public static void removeAllExtraListeners(){
|
||||
getInstance().listenerMap.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package net.kdt.pojavlaunch.extra;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Listener class for the ExtraCore
|
||||
* An ExtraListener can listen to a virtually unlimited amount of values
|
||||
*/
|
||||
public interface ExtraListener<T> {
|
||||
|
||||
/**
|
||||
* Called upon a new value being set
|
||||
* @param key The name of the value
|
||||
* @param value The new value as a string
|
||||
* @return Whether you consume the Listener (stop listening)
|
||||
*/
|
||||
boolean onValueSet(String key, @Nullable T value);
|
||||
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package net.kdt.pojavlaunch.multirt;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import net.kdt.pojavlaunch.BaseLauncherActivity;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
public class MultiRTConfigDialog {
|
||||
public static final int MULTIRT_PICK_RUNTIME = 2048;
|
||||
public static final int MULTIRT_PICK_RUNTIME_STARTUP = 2049;
|
||||
public AlertDialog dialog;
|
||||
public RecyclerView dialogView;
|
||||
public void prepare(BaseLauncherActivity ctx) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(R.string.multirt_config_title);
|
||||
dialogView = new RecyclerView(ctx);
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(ctx);
|
||||
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
|
||||
dialogView.setLayoutManager(linearLayoutManager);
|
||||
dialogView.setAdapter(new RTRecyclerViewAdapter(this));
|
||||
builder.setView(dialogView);
|
||||
builder.setPositiveButton(R.string.multirt_config_add, (dialog, which) -> openRuntimeSelector(ctx,MULTIRT_PICK_RUNTIME));
|
||||
builder.setNegativeButton(R.string.mcn_exit_call, (dialog, which) -> dialog.cancel());
|
||||
dialog = builder.create();
|
||||
}
|
||||
public void refresh() {
|
||||
RecyclerView.Adapter adapter = dialogView.getAdapter();
|
||||
if(adapter != null)dialogView.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
public static void openRuntimeSelector(Activity ctx, int code) {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("xz");
|
||||
if(mimeType == null) mimeType = "*/*";
|
||||
intent.setType(mimeType);
|
||||
ctx.startActivityForResult(intent,code);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,264 +0,0 @@
|
|||
package net.kdt.pojavlaunch.multirt;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.Os;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.utils.JREUtils;
|
||||
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MultiRTUtils {
|
||||
public static HashMap<String,Runtime> cache = new HashMap<>();
|
||||
public static class Runtime {
|
||||
public Runtime(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public String name;
|
||||
public String versionString;
|
||||
public String arch;
|
||||
public int javaVersion;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Runtime runtime = (Runtime) o;
|
||||
return name.equals(runtime.name);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
}
|
||||
public static interface ProgressReporterThingy {
|
||||
void reportStringProgress(int resid, Object ... stuff);
|
||||
}
|
||||
private static final File runtimeFolder = new File(Tools.MULTIRT_HOME);
|
||||
private static final String JAVA_VERSION_str = "JAVA_VERSION=\"";
|
||||
private static final String OS_ARCH_str = "OS_ARCH=\"";
|
||||
public static List<Runtime> getRuntimes() {
|
||||
if(!runtimeFolder.exists()) runtimeFolder.mkdirs();
|
||||
ArrayList<Runtime> ret = new ArrayList<>();
|
||||
System.out.println("Fetch runtime list");
|
||||
for(File f : runtimeFolder.listFiles()) {
|
||||
ret.add(read(f.getName()));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
public static String getNearestJREName(int majorVersion) {
|
||||
List<Runtime> runtimes = getRuntimes();
|
||||
int diff_factor = Integer.MAX_VALUE;
|
||||
String result = null;
|
||||
for(Runtime r : runtimes) {
|
||||
if(r.javaVersion >= majorVersion) { // lower - not useful
|
||||
int currentFactor = r.javaVersion - majorVersion;
|
||||
if(diff_factor > currentFactor) {
|
||||
result = r.name;
|
||||
diff_factor = currentFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public static void installRuntimeNamed(InputStream runtimeInputStream, String name, ProgressReporterThingy thingy) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
File tmp = new File(dest,"temporary");
|
||||
if(dest.exists()) FileUtils.deleteDirectory(dest);
|
||||
dest.mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(tmp);
|
||||
thingy.reportStringProgress(R.string.multirt_progress_caching);
|
||||
IOUtils.copy(runtimeInputStream,fos);
|
||||
fos.close();
|
||||
runtimeInputStream.close();
|
||||
uncompressTarXZ(tmp,dest,thingy);
|
||||
tmp.delete();
|
||||
read(name);
|
||||
}
|
||||
private static void __installRuntimeNamed__NoRM(InputStream runtimeInputStream, File dest, ProgressReporterThingy thingy) throws IOException {
|
||||
File tmp = new File(dest,"temporary");
|
||||
FileOutputStream fos = new FileOutputStream(tmp);
|
||||
thingy.reportStringProgress(R.string.multirt_progress_caching);
|
||||
IOUtils.copy(runtimeInputStream,fos);
|
||||
fos.close();
|
||||
runtimeInputStream.close();
|
||||
uncompressTarXZ(tmp,dest,thingy);
|
||||
tmp.delete();
|
||||
}
|
||||
public static void postPrepare(Context ctx, String name) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
if(!dest.exists()) return;
|
||||
Runtime r = read(name);
|
||||
String libFolder = "lib";
|
||||
if(new File(dest,libFolder+"/"+r.arch).exists()) libFolder = libFolder+"/"+r.arch;
|
||||
File ftIn = new File(dest, libFolder+ "/libfreetype.so.6");
|
||||
File ftOut = new File(dest, libFolder + "/libfreetype.so");
|
||||
if (ftIn.exists() && (!ftOut.exists() || ftIn.length() != ftOut.length())) {
|
||||
ftIn.renameTo(ftOut);
|
||||
}
|
||||
|
||||
// Refresh libraries
|
||||
copyDummyNativeLib(ctx,"libawt_xawt.so",dest,libFolder);
|
||||
}
|
||||
private static void copyDummyNativeLib(Context ctx, String name, File dest, String libFolder) throws IOException {
|
||||
|
||||
File fileLib = new File(dest, "/"+libFolder + "/" + name);
|
||||
fileLib.delete();
|
||||
FileInputStream is = new FileInputStream(new File(ctx.getApplicationInfo().nativeLibraryDir, name));
|
||||
FileOutputStream os = new FileOutputStream(fileLib);
|
||||
IOUtils.copy(is, os);
|
||||
is.close();
|
||||
os.close();
|
||||
}
|
||||
public static Runtime installRuntimeNamedBinpack(InputStream universalFileInputStream, InputStream platformBinsInputStream, String name, String binpackVersion, ProgressReporterThingy thingy) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
if(dest.exists()) FileUtils.deleteDirectory(dest);
|
||||
dest.mkdirs();
|
||||
__installRuntimeNamed__NoRM(universalFileInputStream,dest,thingy);
|
||||
__installRuntimeNamed__NoRM(platformBinsInputStream,dest,thingy);
|
||||
File binpack_verfile = new File(runtimeFolder,"/"+name+"/pojav_version");
|
||||
FileOutputStream fos = new FileOutputStream(binpack_verfile);
|
||||
fos.write(binpackVersion.getBytes());
|
||||
fos.close();
|
||||
cache.remove(name); // Force reread
|
||||
return read(name);
|
||||
}
|
||||
public static String __internal__readBinpackVersion(String name) {
|
||||
File binpack_verfile = new File(runtimeFolder,"/"+name+"/pojav_version");
|
||||
try {
|
||||
if (binpack_verfile.exists()) {
|
||||
return Tools.read(binpack_verfile.getAbsolutePath());
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static void removeRuntimeNamed(String name) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
if(dest.exists()) {
|
||||
FileUtils.deleteDirectory(dest);
|
||||
cache.remove(name);
|
||||
}
|
||||
}
|
||||
public static void setRuntimeNamed(Context ctx, String name) throws IOException {
|
||||
File dest = new File(runtimeFolder,"/"+name);
|
||||
if((!dest.exists()) || MultiRTUtils.forceReread(name).versionString == null) throw new RuntimeException("Selected runtime is broken!");
|
||||
Tools.DIR_HOME_JRE = dest.getAbsolutePath();
|
||||
JREUtils.relocateLibPath(ctx);
|
||||
}
|
||||
public static Runtime forceReread(String name) {
|
||||
cache.remove(name);
|
||||
return read(name);
|
||||
}
|
||||
public static Runtime read(String name) {
|
||||
if(cache.containsKey(name)) return cache.get(name);
|
||||
Runtime retur;
|
||||
File release = new File(runtimeFolder,"/"+name+"/release");
|
||||
if(!release.exists()) {
|
||||
return new Runtime(name);
|
||||
}
|
||||
try {
|
||||
String content = Tools.read(release.getAbsolutePath());
|
||||
int _JAVA_VERSION_index = content.indexOf(JAVA_VERSION_str);
|
||||
int _OS_ARCH_index = content.indexOf(OS_ARCH_str);
|
||||
if(_JAVA_VERSION_index != -1 && _OS_ARCH_index != -1) {
|
||||
_JAVA_VERSION_index += JAVA_VERSION_str.length();
|
||||
_OS_ARCH_index += OS_ARCH_str.length();
|
||||
String javaVersion = content.substring(_JAVA_VERSION_index,content.indexOf('"',_JAVA_VERSION_index));
|
||||
String[] javaVersionSplit = javaVersion.split("\\.");
|
||||
int javaVersionInt;
|
||||
if (javaVersionSplit[0].equals("1")) {
|
||||
javaVersionInt = Integer.parseInt(javaVersionSplit[1]);
|
||||
} else {
|
||||
javaVersionInt = Integer.parseInt(javaVersionSplit[0]);
|
||||
}
|
||||
Runtime r = new Runtime(name);
|
||||
r.arch = content.substring(_OS_ARCH_index,content.indexOf('"',_OS_ARCH_index));
|
||||
r.javaVersion = javaVersionInt;
|
||||
r.versionString = javaVersion;
|
||||
retur = r;
|
||||
}else{
|
||||
retur = new Runtime(name);
|
||||
}
|
||||
}catch(IOException e) {
|
||||
retur = new Runtime(name);
|
||||
}
|
||||
cache.put(name,retur);
|
||||
return retur;
|
||||
}
|
||||
private static void uncompressTarXZ(final File tarFile, final File dest, final ProgressReporterThingy thingy) throws IOException {
|
||||
dest.mkdirs();
|
||||
TarArchiveInputStream tarIn = null;
|
||||
|
||||
tarIn = new TarArchiveInputStream(
|
||||
new XZCompressorInputStream(
|
||||
new BufferedInputStream(
|
||||
new FileInputStream(tarFile)
|
||||
)
|
||||
)
|
||||
);
|
||||
TarArchiveEntry tarEntry = tarIn.getNextTarEntry();
|
||||
// tarIn is a TarArchiveInputStream
|
||||
while (tarEntry != null) {
|
||||
/*
|
||||
* Unpacking very small files in short time cause
|
||||
* application to ANR or out of memory, so delay
|
||||
* a little if size is below than 20kb (20480 bytes)
|
||||
*/
|
||||
if (tarEntry.getSize() <= 20480) {
|
||||
try {
|
||||
// 40 small files per second
|
||||
Thread.sleep(25);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
final String tarEntryName = tarEntry.getName();
|
||||
// publishProgress(null, "Unpacking " + tarEntry.getName());
|
||||
thingy.reportStringProgress(R.string.global_unpacking,tarEntryName);
|
||||
File destPath = new File(dest, tarEntry.getName());
|
||||
if (tarEntry.isSymbolicLink()) {
|
||||
destPath.getParentFile().mkdirs();
|
||||
try {
|
||||
// android.system.Os
|
||||
// Libcore one support all Android versions
|
||||
Os.symlink(tarEntry.getName(), tarEntry.getLinkName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else if (tarEntry.isDirectory()) {
|
||||
destPath.mkdirs();
|
||||
destPath.setExecutable(true);
|
||||
} else if (!destPath.exists() || destPath.length() != tarEntry.getSize()) {
|
||||
destPath.getParentFile().mkdirs();
|
||||
destPath.createNewFile();
|
||||
|
||||
FileOutputStream os = new FileOutputStream(destPath);
|
||||
IOUtils.copy(tarIn, os);
|
||||
os.close();
|
||||
|
||||
}
|
||||
tarEntry = tarIn.getNextTarEntry();
|
||||
}
|
||||
tarIn.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
package net.kdt.pojavlaunch.multirt;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class RTRecyclerViewAdapter extends RecyclerView.Adapter<RTRecyclerViewAdapter.RTViewHolder> {
|
||||
MultiRTConfigDialog dialog;
|
||||
public RTRecyclerViewAdapter(MultiRTConfigDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
}
|
||||
@NonNull
|
||||
@Override
|
||||
public RTViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View recyclableView = LayoutInflater.from(parent.getContext()).inflate(R.layout.multirt_recyclable_view,parent,false);
|
||||
return new RTViewHolder(recyclableView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RTViewHolder holder, int position) {
|
||||
final List<MultiRTUtils.Runtime> runtimes = MultiRTUtils.getRuntimes();
|
||||
holder.bindRuntime(runtimes.get(position),position);
|
||||
}
|
||||
public boolean isDefaultRuntime(MultiRTUtils.Runtime rt) {
|
||||
return LauncherPreferences.PREF_DEFAULT_RUNTIME.equals(rt.name);
|
||||
}
|
||||
public void setDefault(MultiRTUtils.Runtime rt){
|
||||
LauncherPreferences.PREF_DEFAULT_RUNTIME = rt.name;
|
||||
LauncherPreferences.DEFAULT_PREF.edit().putString("defaultRuntime",LauncherPreferences.PREF_DEFAULT_RUNTIME).apply();
|
||||
RTRecyclerViewAdapter.this.notifyDataSetChanged();
|
||||
}
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return MultiRTUtils.getRuntimes().size();
|
||||
}
|
||||
public class RTViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
|
||||
final TextView javaVersionView;
|
||||
final TextView fullJavaVersionView;
|
||||
final ColorStateList defaultColors;
|
||||
final Button setDefaultButton;
|
||||
final Context ctx;
|
||||
MultiRTUtils.Runtime currentRuntime;
|
||||
int currentPosition;
|
||||
public RTViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
javaVersionView = itemView.findViewById(R.id.multirt_view_java_version);
|
||||
fullJavaVersionView = itemView.findViewById(R.id.multirt_view_java_version_full);
|
||||
itemView.findViewById(R.id.multirt_view_removebtn).setOnClickListener(this);
|
||||
setDefaultButton = itemView.findViewById(R.id.multirt_view_setdefaultbtn);
|
||||
setDefaultButton.setOnClickListener(this);
|
||||
defaultColors = fullJavaVersionView.getTextColors();
|
||||
ctx = itemView.getContext();
|
||||
}
|
||||
public void bindRuntime(MultiRTUtils.Runtime rt, int pos) {
|
||||
currentRuntime = rt;
|
||||
currentPosition = pos;
|
||||
if(rt.versionString != null) {
|
||||
javaVersionView.setText(ctx.getString(R.string.multirt_java_ver, rt.name, rt.javaVersion));
|
||||
fullJavaVersionView.setText(rt.versionString);
|
||||
fullJavaVersionView.setTextColor(defaultColors);
|
||||
setDefaultButton.setVisibility(View.VISIBLE);
|
||||
boolean default_ = isDefaultRuntime(rt);
|
||||
setDefaultButton.setEnabled(!default_);
|
||||
setDefaultButton.setText(default_?R.string.multirt_config_setdefault_already:R.string.multirt_config_setdefault);
|
||||
}else{
|
||||
javaVersionView.setText(rt.name);
|
||||
fullJavaVersionView.setText(R.string.multirt_runtime_corrupt);
|
||||
fullJavaVersionView.setTextColor(Color.RED);
|
||||
setDefaultButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if(v.getId() == R.id.multirt_view_removebtn) {
|
||||
if (currentRuntime != null) {
|
||||
if(MultiRTUtils.getRuntimes().size() < 2) {
|
||||
AlertDialog.Builder bldr = new AlertDialog.Builder(ctx);
|
||||
bldr.setTitle(R.string.global_error);
|
||||
bldr.setMessage(R.string.multirt_config_removeerror_last);
|
||||
bldr.setPositiveButton(android.R.string.ok,(adapter, which)->adapter.dismiss());
|
||||
bldr.show();
|
||||
return;
|
||||
}
|
||||
|
||||
final ProgressDialog barrier = new ProgressDialog(ctx);
|
||||
barrier.setMessage(ctx.getString(R.string.global_waiting));
|
||||
barrier.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
barrier.setCancelable(false);
|
||||
barrier.show();
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
MultiRTUtils.removeRuntimeNamed(currentRuntime.name);
|
||||
} catch (IOException e) {
|
||||
Tools.showError(itemView.getContext(), e);
|
||||
}
|
||||
v.post(() -> {
|
||||
if(isDefaultRuntime(currentRuntime)) setDefault(MultiRTUtils.getRuntimes().get(0));
|
||||
barrier.dismiss();
|
||||
RTRecyclerViewAdapter.this.notifyDataSetChanged();
|
||||
dialog.dialog.show();
|
||||
});
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
}else if(v.getId() == R.id.multirt_view_setdefaultbtn) {
|
||||
if(currentRuntime != null) {
|
||||
setDefault(currentRuntime);
|
||||
RTRecyclerViewAdapter.this.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
package net.kdt.pojavlaunch.multirt;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.DataSetObserver;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RTSpinnerAdapter implements SpinnerAdapter {
|
||||
final Context ctx;
|
||||
List<MultiRTUtils.Runtime> runtimes;
|
||||
public RTSpinnerAdapter(@NonNull Context context, List<MultiRTUtils.Runtime> runtimes) {
|
||||
this.runtimes = runtimes;
|
||||
MultiRTUtils.Runtime runtime = new MultiRTUtils.Runtime("<Default>");
|
||||
runtime.versionString = "";
|
||||
this.runtimes.add(runtime);
|
||||
ctx = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return runtimes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return runtimes.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return runtimes.get(position).name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
View v = convertView!=null?
|
||||
convertView:
|
||||
LayoutInflater.from(ctx).inflate(R.layout.multirt_recyclable_view,parent,false);
|
||||
|
||||
MultiRTUtils.Runtime rt = runtimes.get(position);
|
||||
|
||||
final TextView javaVersionView = v.findViewById(R.id.multirt_view_java_version);
|
||||
final TextView fullJavaVersionView = v.findViewById(R.id.multirt_view_java_version_full);
|
||||
v.findViewById(R.id.multirt_view_removebtn).setVisibility(View.GONE);
|
||||
v.findViewById(R.id.multirt_view_setdefaultbtn).setVisibility(View.GONE);
|
||||
|
||||
if(rt.versionString != null) {
|
||||
javaVersionView.setText(ctx.getString(R.string.multirt_java_ver, rt.name, rt.javaVersion));
|
||||
fullJavaVersionView.setText(rt.versionString);
|
||||
}else{
|
||||
javaVersionView.setText(rt.name);
|
||||
fullJavaVersionView.setText(R.string.multirt_runtime_corrupt);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return runtimes.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
||||
return getView(position,convertView,parent);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
package net.kdt.pojavlaunch.prefs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.multirt.RTSpinnerAdapter;
|
||||
import net.kdt.pojavlaunch.value.PerVersionConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class PerVersionConfigDialog{
|
||||
final Context ctx;
|
||||
final AlertDialog dialog;
|
||||
final View v;
|
||||
List<MultiRTUtils.Runtime> runtimes;
|
||||
final Spinner javaVMSpinner;
|
||||
final Spinner rendererSpinner;
|
||||
final EditText customDirText;
|
||||
final EditText jvmArgsEditText;
|
||||
final List<String> renderNames;
|
||||
String selectedGameVersion = null;
|
||||
public PerVersionConfigDialog(Context _ctx) {
|
||||
ctx = _ctx;
|
||||
v = LayoutInflater.from(ctx).inflate(R.layout.pvc_popup,null);
|
||||
javaVMSpinner = v.findViewById(R.id.pvc_javaVm);
|
||||
rendererSpinner = v.findViewById(R.id.pvc_renderer);
|
||||
{
|
||||
List<String> renderList = new ArrayList<>();
|
||||
Collections.addAll(renderList, ctx.getResources().getStringArray(R.array.renderer));
|
||||
renderList.add("Default");
|
||||
renderNames = Arrays.asList(ctx.getResources().getStringArray(R.array.renderer_values));
|
||||
rendererSpinner.setAdapter(new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_dropdown_item,renderList));
|
||||
}
|
||||
customDirText = v.findViewById(R.id.pvc_customDir);
|
||||
jvmArgsEditText = v.findViewById(R.id.pvc_jvmArgs);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setView(v);
|
||||
builder.setTitle(R.string.pvc_title);
|
||||
builder.setNegativeButton(android.R.string.cancel,(dialogInterface,i)->dialogInterface.dismiss());
|
||||
builder.setPositiveButton(android.R.string.ok,this::save);
|
||||
dialog = builder.create();
|
||||
}
|
||||
public void refreshRuntimes() {
|
||||
if(runtimes!=null)runtimes.clear();
|
||||
runtimes = MultiRTUtils.getRuntimes();
|
||||
//runtimes.add(new MultiRTUtils.Runtime("<Default>"));
|
||||
}
|
||||
private void save(DialogInterface i, int which) {
|
||||
if(selectedGameVersion == null) {
|
||||
i.dismiss();
|
||||
return;
|
||||
}
|
||||
PerVersionConfig.VersionConfig conf1 = PerVersionConfig.configMap.get(selectedGameVersion);
|
||||
if(conf1==null){
|
||||
conf1=new PerVersionConfig.VersionConfig();
|
||||
}
|
||||
conf1.jvmArgs=jvmArgsEditText.getText().toString();
|
||||
conf1.gamePath=customDirText.getText().toString();
|
||||
|
||||
if(rendererSpinner.getSelectedItemPosition() == renderNames.size()) conf1.renderer = null;
|
||||
else conf1.renderer = renderNames.get(rendererSpinner.getSelectedItemPosition());
|
||||
|
||||
String runtime=((MultiRTUtils.Runtime)javaVMSpinner.getSelectedItem()).name;;
|
||||
if(!runtime.equals("<Default>"))conf1.selectedRuntime=runtime;
|
||||
else conf1.selectedRuntime=null;
|
||||
|
||||
PerVersionConfig.configMap.put(selectedGameVersion,conf1);
|
||||
try{
|
||||
PerVersionConfig.update();
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
public boolean openConfig(String selectedVersion) {
|
||||
selectedGameVersion = selectedVersion;
|
||||
try{
|
||||
PerVersionConfig.update();
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
PerVersionConfig.VersionConfig conf=PerVersionConfig.configMap.get(selectedGameVersion);
|
||||
refreshRuntimes();
|
||||
javaVMSpinner.setAdapter(new RTSpinnerAdapter(ctx,runtimes));
|
||||
{
|
||||
int jvm_index = runtimes.indexOf(new MultiRTUtils.Runtime("<Default>"));
|
||||
int rnd_index = rendererSpinner.getAdapter().getCount()-1;
|
||||
if (conf != null) {
|
||||
customDirText.setText(conf.gamePath);
|
||||
jvmArgsEditText.setText(conf.jvmArgs);
|
||||
if (conf.selectedRuntime != null) {
|
||||
int nindex = runtimes.indexOf(new MultiRTUtils.Runtime(conf.selectedRuntime));
|
||||
if (nindex != -1) jvm_index = nindex;
|
||||
}
|
||||
if(conf.renderer != null) {
|
||||
int nindex = renderNames.indexOf(conf.renderer);
|
||||
if (nindex != -1) rnd_index = nindex;
|
||||
}
|
||||
}
|
||||
javaVMSpinner.setSelection(jvm_index);
|
||||
rendererSpinner.setSelection(rnd_index);
|
||||
}
|
||||
dialog.show();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package net.kdt.pojavlaunch.prefs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import net.kdt.pojavlaunch.BaseLauncherActivity;
|
||||
|
||||
public class RuntimeManagerPreference extends Preference{
|
||||
public RuntimeManagerPreference(Context ctx) {
|
||||
this(ctx, null);
|
||||
}
|
||||
|
||||
public RuntimeManagerPreference(Context ctx, AttributeSet attrs) {
|
||||
super(ctx, attrs);
|
||||
setPersistent(false);
|
||||
}
|
||||
@Override
|
||||
protected void onClick() {
|
||||
super.onClick();
|
||||
((BaseLauncherActivity)this.getContext()).mRuntimeConfigDialog.dialog.show();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
package net.kdt.pojavlaunch.prefs.screens;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
|
||||
public class LauncherPreferenceControlFragment extends LauncherPreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle b, String str) {
|
||||
// Get values
|
||||
int longPressTrigger = LauncherPreferences.PREF_LONGPRESS_TRIGGER;
|
||||
int prefButtonSize = (int) LauncherPreferences.PREF_BUTTONSIZE;
|
||||
int mouseScale = (int) LauncherPreferences.PREF_MOUSESCALE;
|
||||
float mouseSpeed = LauncherPreferences.PREF_MOUSESPEED;
|
||||
|
||||
//Triggers a write for some reason which resets the value
|
||||
addPreferencesFromResource(R.xml.pref_control);
|
||||
|
||||
CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger");
|
||||
seek2.setRange(100, 1000);
|
||||
seek2.setValue(longPressTrigger);
|
||||
seek2.setSuffix(" ms");
|
||||
|
||||
CustomSeekBarPreference seek3 = findPreference("buttonscale");
|
||||
seek3.setRange(80, 250);
|
||||
seek3.setValue(prefButtonSize);
|
||||
seek3.setSuffix(" %");
|
||||
|
||||
CustomSeekBarPreference seek4 = findPreference("mousescale");
|
||||
seek4.setRange(25, 300);
|
||||
seek4.setValue(mouseScale);
|
||||
seek4.setSuffix(" %");
|
||||
|
||||
CustomSeekBarPreference seek6 = findPreference("mousespeed");
|
||||
seek6.setRange(25, 300);
|
||||
seek6.setValue((int)(mouseSpeed *100f));
|
||||
seek6.setSuffix(" %");
|
||||
|
||||
|
||||
computeVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences p, String s) {
|
||||
super.onSharedPreferenceChanged(p, s);
|
||||
computeVisibility();
|
||||
}
|
||||
|
||||
private void computeVisibility(){
|
||||
CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger");
|
||||
seek2.setVisible(!LauncherPreferences.PREF_DISABLE_GESTURES);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package net.kdt.pojavlaunch.prefs.screens;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
public class LauncherPreferenceExperimentalFragment extends LauncherPreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle b, String str) {
|
||||
addPreferencesFromResource(R.xml.pref_experimental);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package net.kdt.pojavlaunch.prefs.screens;
|
||||
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.*;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.*;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
|
||||
import android.content.*;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Preference for the main screen, any sub-screen should inherit this class for consistent behavior,
|
||||
* overriding only onCreatePreferences
|
||||
*/
|
||||
public class LauncherPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
view.setBackgroundColor(Color.parseColor("#232323"));
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle b, String str) {
|
||||
addPreferencesFromResource(R.xml.pref_main);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences p, String s) {
|
||||
LauncherPreferences.loadPreferences(getContext());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package net.kdt.pojavlaunch.prefs.screens;
|
||||
|
||||
import static net.kdt.pojavlaunch.Architecture.is32BitsDevice;
|
||||
import static net.kdt.pojavlaunch.Tools.getTotalDeviceMemory;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.EditTextPreference;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
|
||||
public class LauncherPreferenceJavaFragment extends LauncherPreferenceFragment {
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle b, String str) {
|
||||
int ramAllocation = LauncherPreferences.PREF_RAM_ALLOCATION;
|
||||
|
||||
// Triggers a write for some reason
|
||||
addPreferencesFromResource(R.xml.pref_java);
|
||||
|
||||
int maxRAM;
|
||||
int deviceRam = getTotalDeviceMemory(getContext());
|
||||
|
||||
CustomSeekBarPreference seek7 = findPreference("allocation");
|
||||
seek7.setMin(256);
|
||||
|
||||
if(is32BitsDevice()) maxRAM = Math.min(1100, deviceRam);
|
||||
else maxRAM = deviceRam - (deviceRam < 3064 ? 800 : 1024); //To have a minimum for the device to breathe
|
||||
|
||||
seek7.setMax(maxRAM);
|
||||
seek7.setValue(ramAllocation);
|
||||
seek7.setSuffix(" MB");
|
||||
|
||||
|
||||
EditTextPreference editJVMArgs = findPreference("javaArgs");
|
||||
if (editJVMArgs != null) {
|
||||
editJVMArgs.setOnBindEditTextListener(TextView::setSingleLine);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package net.kdt.pojavlaunch.prefs.screens;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
|
||||
public class LauncherPreferenceMiscellaneousFragment extends LauncherPreferenceFragment {
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle b, String str) {
|
||||
addPreferencesFromResource(R.xml.pref_misc);
|
||||
|
||||
// Sustained performance is only available since Nougat
|
||||
SwitchPreference sustainedPerfSwitch = findPreference("sustainedPerformance");
|
||||
sustainedPerfSwitch.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package net.kdt.pojavlaunch.prefs.screens;
|
||||
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference;
|
||||
|
||||
/**
|
||||
* Fragment for any settings video related
|
||||
*/
|
||||
public class LauncherPreferenceVideoFragment extends LauncherPreferenceFragment {
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle b, String str) {
|
||||
addPreferencesFromResource(R.xml.pref_video);
|
||||
|
||||
//Disable notch checking behavior on android 8.1 and below.
|
||||
findPreference("ignoreNotch").setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && PREF_NOTCH_SIZE != 0);
|
||||
|
||||
CustomSeekBarPreference seek5 = findPreference("resolutionRatio");
|
||||
seek5.setMin(25);
|
||||
seek5.setSuffix(" %");
|
||||
|
||||
// #724 bug fix
|
||||
if (seek5.getValue() < 25) {
|
||||
seek5.setValue(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
package net.kdt.pojavlaunch.scoped;
|
||||
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class GameFolderProvider extends DocumentsProvider {
|
||||
static File baseDir = new File(Tools.DIR_GAME_HOME);
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_MIME_TYPES,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES
|
||||
};
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_SIZE
|
||||
};
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Root.COLUMN_ROOT_ID, baseDir.getAbsolutePath());
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, baseDir.getAbsolutePath());
|
||||
row.add(Root.COLUMN_SUMMARY, null);
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH);
|
||||
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
|
||||
row.add(Root.COLUMN_MIME_TYPES, "*/*");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, baseDir.getFreeSpace());
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
includeFile(result,documentId,null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
for(File f : new File(parentDocumentId).listFiles()) {
|
||||
includeFile(result,null,f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
|
||||
return ParcelFileDescriptor.open(new File(documentId),ParcelFileDescriptor.parseMode(mode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
File f = new File(documentId);
|
||||
return new AssetFileDescriptor(ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY), 0, f.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
if (!new File(documentId).delete()) throw new FileNotFoundException("Can't remove "+documentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
return getMimeType(new File(documentId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = new File(rootId);
|
||||
final LinkedList<File> pending = new LinkedList<>();
|
||||
pending.add(parent);
|
||||
|
||||
final int MAX_SEARCH_RESULTS = 50;
|
||||
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
|
||||
final File file = pending.removeFirst();
|
||||
boolean isInsideGameDir;
|
||||
try {
|
||||
isInsideGameDir = file.getCanonicalPath().startsWith(baseDir.getCanonicalPath());
|
||||
} catch (IOException e) {
|
||||
isInsideGameDir = true;
|
||||
}
|
||||
if (isInsideGameDir) {
|
||||
if (file.isDirectory()) {
|
||||
Collections.addAll(pending, file.listFiles());
|
||||
} else {
|
||||
if (file.getName().toLowerCase().contains(query)) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
private static String getMimeType(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
final String name = file.getName();
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1);
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) return mime;
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
private void includeFile(MatrixCursor result, String docId, File file)
|
||||
throws FileNotFoundException {
|
||||
if (docId == null) {
|
||||
docId = file.getAbsolutePath();
|
||||
} else {
|
||||
file = new File(docId);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
if (file.isDirectory()) {
|
||||
if (file.isDirectory() && file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
} else if (file.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE;
|
||||
}
|
||||
|
||||
final String displayName = file.getName();
|
||||
final String mimeType = getMimeType(file);
|
||||
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,473 +0,0 @@
|
|||
package net.kdt.pojavlaunch.tasks;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import android.util.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
import org.apache.commons.io.*;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable>
|
||||
{
|
||||
private BaseLauncherActivity mActivity;
|
||||
private boolean launchWithError = false;
|
||||
MinecraftDownloaderTask thiz = this;
|
||||
public MinecraftDownloaderTask(BaseLauncherActivity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
mActivity.mLaunchProgress.setMax(1);
|
||||
mActivity.statusIsLaunching(true);
|
||||
}
|
||||
|
||||
private JMinecraftVersionList.Version verInfo;
|
||||
@Override
|
||||
protected Throwable doInBackground(final String[] p1) {
|
||||
Throwable throwable = null;
|
||||
try {
|
||||
final String downVName = "/" + p1[0] + "/" + p1[0];
|
||||
//Downloading libraries
|
||||
String minecraftMainJar = Tools.DIR_HOME_VERSION + downVName + ".jar";
|
||||
JAssets assets = null;
|
||||
try {
|
||||
File verJsonDir = new File(Tools.DIR_HOME_VERSION + downVName + ".json");
|
||||
|
||||
verInfo = findVersion(p1[0]);
|
||||
|
||||
if (verInfo.url != null) {
|
||||
boolean isManifestGood = true; // assume it is dy default
|
||||
if(verJsonDir.exists()) {//if the file exists
|
||||
if(LauncherPreferences.PREF_CHECK_LIBRARY_SHA) {// check if the checker is on
|
||||
if (verInfo.sha1 != null) {//check if the SHA is available
|
||||
if (Tools.compareSHA1(verJsonDir, verInfo.sha1)) // check the SHA
|
||||
publishProgress("0", mActivity.getString(R.string.dl_library_sha_pass, p1[0] + ".json")); // and say that SHA was verified
|
||||
else{
|
||||
verJsonDir.delete(); // if it wasn't, delete the old one
|
||||
publishProgress("0", mActivity.getString(R.string.dl_library_sha_fail, p1[0] + ".json")); // put it into log
|
||||
isManifestGood = false; // and mark manifest as bad
|
||||
}
|
||||
}else{
|
||||
publishProgress("0", mActivity.getString(R.string.dl_library_sha_unknown, p1[0] + ".json")); // say that the sha is unknown, but assume that the manifest is good
|
||||
}
|
||||
}else{
|
||||
Log.w("Chk","Checker is off");// if the checker is off, manifest will be always good, unless it doesn't exist
|
||||
}
|
||||
} else {
|
||||
isManifestGood = false;
|
||||
}
|
||||
if(!isManifestGood) {
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, p1[0] + ".json"));
|
||||
Tools.downloadFileMonitored(
|
||||
verInfo.url,
|
||||
verJsonDir.getAbsolutePath(),
|
||||
new Tools.DownloaderFeedback() {
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
publishDownloadProgress(p1[0] + ".json", curr, max);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
verInfo = Tools.getVersionInfo(mActivity,p1[0]);
|
||||
|
||||
//Now we have the reliable information to check if our runtime settings are good enough
|
||||
if(verInfo.javaVersion != null) { //1.17+
|
||||
PerVersionConfig.update();
|
||||
PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(p1[0]);
|
||||
if(cfg == null) {
|
||||
cfg = new PerVersionConfig.VersionConfig();
|
||||
PerVersionConfig.configMap.put(p1[0],cfg);
|
||||
}
|
||||
MultiRTUtils.Runtime r = cfg.selectedRuntime != null?MultiRTUtils.read(cfg.selectedRuntime):MultiRTUtils.read(LauncherPreferences.PREF_DEFAULT_RUNTIME);
|
||||
if(r.javaVersion < verInfo.javaVersion.majorVersion) {
|
||||
String appropriateRuntime = MultiRTUtils.getNearestJREName(verInfo.javaVersion.majorVersion);
|
||||
if(appropriateRuntime != null) {
|
||||
cfg.selectedRuntime = appropriateRuntime;
|
||||
PerVersionConfig.update();
|
||||
}else{
|
||||
mActivity.runOnUiThread(()->{
|
||||
AlertDialog.Builder bldr = new AlertDialog.Builder(mActivity);
|
||||
bldr.setTitle(R.string.global_error);
|
||||
bldr.setMessage(R.string.multirt_nocompartiblert);
|
||||
bldr.setPositiveButton(android.R.string.ok,(dialog, which)->{
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.show();
|
||||
});
|
||||
throw new SilentException();
|
||||
}
|
||||
} //if else, we are satisfied
|
||||
}
|
||||
{ //run the checks to detect if we have a *brand new* engine
|
||||
int mcReleaseDate = Integer.parseInt(verInfo.releaseTime.substring(0, 10).replace("-", ""));
|
||||
if(mcReleaseDate > 20210225 && verInfo.javaVersion != null && verInfo.javaVersion.majorVersion > 15)
|
||||
V117CompatUtil.runCheck(p1[0],mActivity);
|
||||
}
|
||||
try {
|
||||
assets = downloadIndex(verInfo.assets, new File(Tools.ASSETS_PATH, "indexes/" + verInfo.assets + ".json"));
|
||||
} catch (IOException e) {
|
||||
publishProgress("0", Log.getStackTraceString(e));
|
||||
}
|
||||
|
||||
File outLib;
|
||||
|
||||
|
||||
setMax(verInfo.libraries.length);
|
||||
zeroProgress();
|
||||
for (final DependentLibrary libItem : verInfo.libraries) {
|
||||
|
||||
if (
|
||||
// libItem.name.startsWith("net.java.jinput") ||
|
||||
libItem.name.startsWith("org.lwjgl")
|
||||
) { // Black list
|
||||
publishProgress("1", "Ignored " + libItem.name);
|
||||
//Thread.sleep(100);
|
||||
} else {
|
||||
String[] libInfo = libItem.name.split(":");
|
||||
String libArtifact = Tools.artifactToPath(libInfo[0], libInfo[1], libInfo[2]);
|
||||
outLib = new File(Tools.DIR_HOME_LIBRARY + "/" + libArtifact);
|
||||
outLib.getParentFile().mkdirs();
|
||||
|
||||
if (!outLib.exists()) {
|
||||
downloadLibrary(libItem,libArtifact,outLib);
|
||||
}else{
|
||||
if(libItem.downloads != null && libItem.downloads.artifact != null && libItem.downloads.artifact.sha1 != null && !libItem.downloads.artifact.sha1.isEmpty()) {
|
||||
if(!Tools.compareSHA1(outLib,libItem.downloads.artifact.sha1)) {
|
||||
outLib.delete();
|
||||
publishProgress("0", mActivity.getString(R.string.dl_library_sha_fail,libItem.name));
|
||||
downloadLibrary(libItem,libArtifact,outLib);
|
||||
}else{
|
||||
publishProgress("0", mActivity.getString(R.string.dl_library_sha_pass,libItem.name));
|
||||
}
|
||||
}else{
|
||||
publishProgress("0", mActivity.getString(R.string.dl_library_sha_unknown,libItem.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setMax(3);
|
||||
zeroProgress();
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, p1[0] + ".jar"));
|
||||
File minecraftMainFile = new File(minecraftMainJar);
|
||||
if ((!minecraftMainFile.exists() || minecraftMainFile.length() == 0l) &&
|
||||
verInfo.downloads != null) {
|
||||
try {
|
||||
Tools.downloadFileMonitored(
|
||||
verInfo.downloads.values().toArray(new MinecraftClientInfo[0])[0].url,
|
||||
minecraftMainJar,
|
||||
new Tools.DownloaderFeedback() {
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
publishDownloadProgress(p1[0] + ".jar", curr, max);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Throwable th) {
|
||||
if (verInfo.inheritsFrom != null) {
|
||||
minecraftMainFile.delete();
|
||||
FileInputStream is = new FileInputStream(new File(Tools.DIR_HOME_VERSION, verInfo.inheritsFrom + "/" + verInfo.inheritsFrom + ".jar"));
|
||||
FileOutputStream os = new FileOutputStream(minecraftMainFile);
|
||||
IOUtils.copy(is, os);
|
||||
is.close();
|
||||
os.close();
|
||||
} else {
|
||||
throw th;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
launchWithError = true;
|
||||
throw e;
|
||||
}
|
||||
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_cleancache));
|
||||
// new File(inputPath).delete();
|
||||
|
||||
for (File f : new File(Tools.DIR_HOME_VERSION).listFiles()) {
|
||||
if(f.getName().endsWith(".part")) {
|
||||
Log.d(Tools.APP_NAME, "Cleaning cache: " + f);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
|
||||
mActivity.mIsAssetsProcessing = true;
|
||||
mActivity.mPlayButton.post(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mActivity.mPlayButton.setText("Skip");
|
||||
mActivity.mPlayButton.setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (assets == null) {
|
||||
return null;
|
||||
}
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_download_assets));
|
||||
setMax(assets.objects.size());
|
||||
zeroProgress();
|
||||
try {
|
||||
downloadAssets(assets, verInfo.assets, assets.map_to_resources ? new File(Tools.OBSOLETE_RESOURCES_PATH) : new File(Tools.ASSETS_PATH));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// Ignore it
|
||||
launchWithError = false;
|
||||
} finally {
|
||||
mActivity.mIsAssetsProcessing = false;
|
||||
}
|
||||
} catch (Throwable th){
|
||||
throwable = th;
|
||||
} finally {
|
||||
return throwable;
|
||||
}
|
||||
}
|
||||
private int addProgress = 0;
|
||||
public static class SilentException extends Exception{}
|
||||
public void zeroProgress() {
|
||||
addProgress = 0;
|
||||
}
|
||||
protected void downloadLibrary(DependentLibrary libItem,String libArtifact,File outLib) throws Throwable{
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, libItem.name));
|
||||
String libPathURL;
|
||||
boolean skipIfFailed = false;
|
||||
|
||||
if (libItem.downloads == null || libItem.downloads.artifact == null) {
|
||||
System.out.println("UnkLib:"+libArtifact);
|
||||
MinecraftLibraryArtifact artifact = new MinecraftLibraryArtifact();
|
||||
artifact.url = (libItem.url == null ? "https://libraries.minecraft.net/" : libItem.url.replace("http://","https://")) + libArtifact;
|
||||
libItem.downloads = new DependentLibrary.LibraryDownloads(artifact);
|
||||
|
||||
skipIfFailed = true;
|
||||
}
|
||||
try {
|
||||
libPathURL = libItem.downloads.artifact.url;
|
||||
boolean isFileGood = false;
|
||||
byte timesChecked=0;
|
||||
while(!isFileGood) {
|
||||
timesChecked++;
|
||||
if(timesChecked > 5) throw new RuntimeException("Library download failed after 5 retries");
|
||||
Tools.downloadFileMonitored(
|
||||
libPathURL,
|
||||
outLib.getAbsolutePath(),
|
||||
new Tools.DownloaderFeedback() {
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
publishDownloadProgress(libItem.name, curr, max);
|
||||
}
|
||||
}
|
||||
);
|
||||
if(libItem.downloads.artifact.sha1 != null) {
|
||||
isFileGood = LauncherPreferences.PREF_CHECK_LIBRARY_SHA ? Tools.compareSHA1(outLib,libItem.downloads.artifact.sha1) : true;
|
||||
if(!isFileGood) publishProgress("0", mActivity.getString(R.string.dl_library_sha_fail,libItem.name));
|
||||
else publishProgress("0", mActivity.getString(R.string.dl_library_sha_pass,libItem.name));
|
||||
}else{
|
||||
publishProgress("0", mActivity.getString(R.string.dl_library_sha_unknown,libItem.name));
|
||||
isFileGood = true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
if (!skipIfFailed) {
|
||||
throw th;
|
||||
} else {
|
||||
th.printStackTrace();
|
||||
publishProgress("0", th.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
public void setMax(final int value)
|
||||
{
|
||||
mActivity.mLaunchProgress.post(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mActivity.mLaunchProgress.setMax(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void publishDownloadProgress(String target, int curr, int max) {
|
||||
// array length > 2 ignores append log on dev console
|
||||
publishProgress("0", mActivity.getString(R.string.mcl_launch_downloading_progress, target,
|
||||
curr / 1024d / 1024d, max / 1024d / 1024d), "");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(String... p1)
|
||||
{
|
||||
int addedProg = Integer.parseInt(p1[0]);
|
||||
if (addedProg != -1) {
|
||||
addProgress = addProgress + addedProg;
|
||||
mActivity.mLaunchProgress.setProgress(addProgress);
|
||||
if(p1[1] != null) mActivity.mLaunchTextStatus.setText(p1[1]);
|
||||
}
|
||||
|
||||
if (p1.length < 3) {
|
||||
//mActivity.mConsoleView.putLog(p1[1] + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Throwable p1)
|
||||
{
|
||||
mActivity.mPlayButton.setText("Play");
|
||||
mActivity.mPlayButton.setEnabled(true);
|
||||
mActivity.mLaunchProgress.setMax(100);
|
||||
mActivity.mLaunchProgress.setProgress(0);
|
||||
mActivity.statusIsLaunching(false);
|
||||
if(p1 != null && !(p1 instanceof SilentException)) {
|
||||
p1.printStackTrace();
|
||||
Tools.showError(mActivity, p1);
|
||||
}
|
||||
if(!launchWithError) {
|
||||
//mActivity.mCrashView.setLastCrash("");
|
||||
|
||||
try {
|
||||
Intent mainIntent = new Intent(mActivity, MainActivity.class /* MainActivity.class */);
|
||||
// mainIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
|
||||
mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||
mainIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||
mActivity.startActivity(mainIntent);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Tools.showError(mActivity, e);
|
||||
}
|
||||
}
|
||||
|
||||
mActivity.mTask = null;
|
||||
}
|
||||
|
||||
public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/";
|
||||
|
||||
public JAssets downloadIndex(String versionName, File output) throws IOException {
|
||||
if (!output.exists()) {
|
||||
output.getParentFile().mkdirs();
|
||||
DownloadUtils.downloadFile(verInfo.assetIndex != null ? verInfo.assetIndex.url : "https://s3.amazonaws.com/Minecraft.Download/indexes/" + versionName + ".json", output);
|
||||
}
|
||||
|
||||
return Tools.GLOBAL_GSON.fromJson(Tools.read(output.getAbsolutePath()), JAssets.class);
|
||||
}
|
||||
|
||||
public void downloadAsset(JAssetInfo asset, File objectsDir, AtomicInteger downloadCounter) throws IOException {
|
||||
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
|
||||
File outFile = new File(objectsDir, assetPath);
|
||||
Tools.downloadFileMonitored(MINECRAFT_RES + assetPath, outFile.getAbsolutePath(), new Tools.DownloaderFeedback() {
|
||||
int prevCurr;
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
downloadCounter.addAndGet(curr - prevCurr);
|
||||
prevCurr = curr;
|
||||
}
|
||||
});
|
||||
}
|
||||
public void downloadAssetMapped(JAssetInfo asset, String assetName, File resDir, AtomicInteger downloadCounter) throws IOException {
|
||||
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
|
||||
File outFile = new File(resDir,"/"+assetName);
|
||||
Tools.downloadFileMonitored(MINECRAFT_RES + assetPath, outFile.getAbsolutePath(), new Tools.DownloaderFeedback() {
|
||||
int prevCurr;
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
downloadCounter.addAndGet(curr - prevCurr);
|
||||
prevCurr = curr;
|
||||
}
|
||||
});
|
||||
}
|
||||
public void downloadAssets(final JAssets assets, String assetsVersion, final File outputDir) throws IOException {
|
||||
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
|
||||
final ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 500, TimeUnit.MILLISECONDS, workQueue);
|
||||
mActivity.mIsAssetsProcessing = true;
|
||||
File hasDownloadedFile = new File(outputDir, "downloaded/" + assetsVersion + ".downloaded");
|
||||
if (!hasDownloadedFile.exists()) {
|
||||
System.out.println("Assets begin time: " + System.currentTimeMillis());
|
||||
Map<String, JAssetInfo> assetsObjects = assets.objects;
|
||||
int assetsSizeBytes=0;
|
||||
AtomicInteger downloadedSize = new AtomicInteger(0);
|
||||
AtomicBoolean localInterrupt = new AtomicBoolean(false);
|
||||
File objectsDir = new File(outputDir, "objects");
|
||||
zeroProgress();
|
||||
for(String assetKey : assetsObjects.keySet()) {
|
||||
if(!mActivity.mIsAssetsProcessing) break;
|
||||
JAssetInfo asset = assetsObjects.get(assetKey);
|
||||
assetsSizeBytes+=asset.size;
|
||||
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
|
||||
File outFile = assets.map_to_resources?new File(objectsDir,"/"+assetKey):new File(objectsDir, assetPath);
|
||||
boolean skip = outFile.exists();// skip if the file exists
|
||||
if(LauncherPreferences.PREF_CHECK_LIBRARY_SHA) //if sha checking is enabled
|
||||
if(skip) skip = Tools.compareSHA1(outFile, asset.hash); //check hash
|
||||
if(skip) {
|
||||
downloadedSize.addAndGet(asset.size);
|
||||
}else{
|
||||
if(outFile.exists()) publishProgress("0",mActivity.getString(R.string.dl_library_sha_fail,assetKey));
|
||||
executor.execute(()->{
|
||||
try {
|
||||
if (!assets.map_to_resources) {
|
||||
downloadAsset(asset, objectsDir, downloadedSize);
|
||||
} else {
|
||||
downloadAssetMapped(asset, assetKey, outputDir, downloadedSize);
|
||||
}
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
localInterrupt.set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
mActivity.mLaunchProgress.setMax(assetsSizeBytes);
|
||||
executor.shutdown();
|
||||
try {
|
||||
int prevDLSize=0;
|
||||
System.out.println("Queue size: "+workQueue.size());
|
||||
while ((!executor.awaitTermination(250, TimeUnit.MILLISECONDS))&&(!localInterrupt.get())&&mActivity.mIsAssetsProcessing) {
|
||||
int DLSize = downloadedSize.get();
|
||||
publishProgress(Integer.toString(DLSize-prevDLSize),null,"");
|
||||
publishDownloadProgress("assets", DLSize, assetsSizeBytes);
|
||||
prevDLSize = downloadedSize.get();
|
||||
}
|
||||
if(mActivity.mIsAssetsProcessing) {
|
||||
System.out.println("Unskipped download done!");
|
||||
if(!hasDownloadedFile.getParentFile().exists())hasDownloadedFile.getParentFile().mkdirs();
|
||||
hasDownloadedFile.createNewFile();
|
||||
}else{
|
||||
System.out.println("Skipped!");
|
||||
}
|
||||
executor.shutdownNow();
|
||||
while (!executor.awaitTermination(250, TimeUnit.MILLISECONDS)) {}
|
||||
System.out.println("Fully shut down!");
|
||||
}catch(InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("Assets end time: " + System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
private JMinecraftVersionList.Version findVersion(String version) {
|
||||
if (mActivity.mVersionList != null) {
|
||||
for (JMinecraftVersionList.Version valueVer: mActivity.mVersionList.versions) {
|
||||
if (valueVer.id.equals(version)) {
|
||||
return valueVer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom version, inherits from base.
|
||||
return Tools.getVersionInfo(mActivity,version);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
package net.kdt.pojavlaunch.tasks;
|
||||
|
||||
import android.os.*;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import android.widget.AdapterView.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
|
||||
public class RefreshVersionListTask extends AsyncTask<Void, Void, ArrayList<String>>
|
||||
{
|
||||
private BaseLauncherActivity mActivity;
|
||||
public RefreshVersionListTask(BaseLauncherActivity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ArrayList<String> doInBackground(Void[] p1)
|
||||
{
|
||||
try {
|
||||
//mActivity.mVersionList = Tools.GLOBAL_GSON.fromJson(DownloadUtils.downloadString(""), JMinecraftVersionList.class);
|
||||
{
|
||||
ArrayList<JMinecraftVersionList.Version> versions = new ArrayList<>();
|
||||
String[] repositories = LauncherPreferences.PREF_VERSION_REPOS.split(";");
|
||||
for (String url : repositories) {
|
||||
JMinecraftVersionList list;
|
||||
Log.i("ExtVL", "Syncing to external: " + url);
|
||||
list = Tools.GLOBAL_GSON.fromJson(DownloadUtils.downloadString(url), JMinecraftVersionList.class);
|
||||
Log.i("ExtVL","Downloaded the version list, len="+list.versions.length);
|
||||
Collections.addAll(versions,list.versions);
|
||||
}
|
||||
mActivity.mVersionList = new JMinecraftVersionList();
|
||||
mActivity.mVersionList.versions = versions.toArray(new JMinecraftVersionList.Version[versions.size()]);
|
||||
Log.i("ExtVL","Final list size: " + mActivity.mVersionList.versions.length);
|
||||
}
|
||||
|
||||
return filter(mActivity.mVersionList.versions, new File(Tools.DIR_HOME_VERSION).listFiles());
|
||||
} catch (Exception e){
|
||||
System.out.println("Refreshing version list failed !");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ArrayList<String> result)
|
||||
{
|
||||
super.onPostExecute(result);
|
||||
final PopupMenu popup = new PopupMenu(mActivity, mActivity.mVersionSelector);
|
||||
popup.getMenuInflater().inflate(R.menu.menu_versionopt, popup.getMenu());
|
||||
|
||||
if(result != null && result.size() > 0) {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity, android.R.layout.simple_spinner_item, result);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
|
||||
mActivity.mVersionSelector.setAdapter(adapter);
|
||||
mActivity.mVersionSelector.setSelection(selectAt(result.toArray(new String[0]), mActivity.mProfile.selectedVersion));
|
||||
} else {
|
||||
mActivity.mVersionSelector.setSelection(selectAt(mActivity.mAvailableVersions, mActivity.mProfile.selectedVersion));
|
||||
}
|
||||
PerVersionConfigDialog dialog = new PerVersionConfigDialog(this.mActivity);
|
||||
mActivity.mVersionSelector.setOnLongClickListener((v)->dialog.openConfig(mActivity.mProfile.selectedVersion));
|
||||
mActivity.mVersionSelector.setOnItemSelectedListener(new OnItemSelectedListener(){
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> p1, View p2, int p3, long p4)
|
||||
{
|
||||
mActivity.mProfile.selectedVersion = p1.getItemAtPosition(p3).toString();
|
||||
|
||||
PojavProfile.setCurrentProfile(mActivity, mActivity.mProfile);
|
||||
if (PojavProfile.isFileType(mActivity)) {
|
||||
try {
|
||||
PojavProfile.setCurrentProfile(mActivity, mActivity.mProfile.save());
|
||||
} catch (IOException e) {
|
||||
Tools.showError(mActivity, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> p1)
|
||||
{
|
||||
// TODO: Implement this method
|
||||
}
|
||||
});
|
||||
/*mActivity.mVersionSelector.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener(){
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> p1, View p2, int p3, long p4)
|
||||
{
|
||||
// Implement copy, remove, reinstall,...
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
*/
|
||||
popup.setOnMenuItemClickListener(item -> true);
|
||||
|
||||
}
|
||||
|
||||
private ArrayList<String> filter(JMinecraftVersionList.Version[] list1, File[] list2) {
|
||||
ArrayList<String> output = new ArrayList<String>();
|
||||
|
||||
for (JMinecraftVersionList.Version value1: list1) {
|
||||
if ((value1.type.equals("release") && LauncherPreferences.PREF_VERTYPE_RELEASE) ||
|
||||
(value1.type.equals("snapshot") && LauncherPreferences.PREF_VERTYPE_SNAPSHOT) ||
|
||||
(value1.type.equals("old_alpha") && LauncherPreferences.PREF_VERTYPE_OLDALPHA) ||
|
||||
(value1.type.equals("old_beta") && LauncherPreferences.PREF_VERTYPE_OLDBETA) ||
|
||||
(value1.type.equals("modified"))) {
|
||||
output.add(value1.id);
|
||||
}
|
||||
}
|
||||
|
||||
if(list2 != null) for (File value2: list2) {
|
||||
if (!output.contains(value2.getName())) {
|
||||
output.add(value2.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private int selectAt(String[] strArr, String select) {
|
||||
int count = 0;
|
||||
for(String str : strArr){
|
||||
if (str.equals(select)) {
|
||||
return count;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
package net.kdt.pojavlaunch.utils;
|
||||
import static org.lwjgl.glfw.CallbackBridge.windowHeight;
|
||||
import static org.lwjgl.glfw.CallbackBridge.windowWidth;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.FileObserver;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class MCOptionUtils
|
||||
{
|
||||
private static final HashMap<String,String> parameterMap = new HashMap<>();
|
||||
private static final ArrayList<WeakReference<MCOptionListener>> optionListeners = new ArrayList<>();
|
||||
private static FileObserver fileObserver;
|
||||
public interface MCOptionListener {
|
||||
/** Called when an option is changed. Don't know which one though */
|
||||
void onOptionChanged();
|
||||
}
|
||||
|
||||
public static void load() {
|
||||
if(fileObserver == null){
|
||||
setupFileObserver();
|
||||
}
|
||||
|
||||
parameterMap.clear();
|
||||
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new FileReader(Tools.DIR_GAME_NEW + "/options.txt"));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
int firstColonIndex = line.indexOf(':');
|
||||
if(firstColonIndex < 0) {
|
||||
Log.w(Tools.APP_NAME, "No colon on line \""+line+"\", skipping");
|
||||
continue;
|
||||
}
|
||||
parameterMap.put(line.substring(0,firstColonIndex), line.substring(firstColonIndex+1));
|
||||
}
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
Log.w(Tools.APP_NAME, "Could not load options.txt", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void set(String key, String value) {
|
||||
parameterMap.put(key,value);
|
||||
}
|
||||
|
||||
public static String get(String key){
|
||||
return parameterMap.get(key);
|
||||
}
|
||||
|
||||
public static void save() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for(String key : parameterMap.keySet())
|
||||
result.append(key)
|
||||
.append(':')
|
||||
.append(parameterMap.get(key))
|
||||
.append('\n');
|
||||
|
||||
try {
|
||||
Tools.write(Tools.DIR_GAME_NEW + "/options.txt", result.toString());
|
||||
} catch (IOException e) {
|
||||
Log.w(Tools.APP_NAME, "Could not save options.txt", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return The stored Minecraft GUI scale, also auto-computed if on auto-mode or improper setting */
|
||||
public static int getMcScale() {
|
||||
MCOptionUtils.load();
|
||||
String str = MCOptionUtils.get("guiScale");
|
||||
int guiScale = (str == null ? 0 :Integer.parseInt(str));
|
||||
|
||||
int scale = Math.max(Math.min(windowWidth / 320, windowHeight / 240), 1);
|
||||
if(scale < guiScale || guiScale == 0){
|
||||
guiScale = scale;
|
||||
}
|
||||
|
||||
return guiScale;
|
||||
}
|
||||
|
||||
private static void setupFileObserver(){
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
|
||||
fileObserver = new FileObserver(new File(Tools.DIR_GAME_NEW + "/options.txt"), FileObserver.MODIFY) {
|
||||
@Override
|
||||
public void onEvent(int i, @Nullable String s) {
|
||||
MCOptionUtils.load();
|
||||
notifyListeners();
|
||||
}
|
||||
};
|
||||
}else{
|
||||
fileObserver = new FileObserver(Tools.DIR_GAME_NEW + "/options.txt", FileObserver.MODIFY) {
|
||||
@Override
|
||||
public void onEvent(int i, @Nullable String s) {
|
||||
MCOptionUtils.load();
|
||||
notifyListeners();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fileObserver.startWatching();
|
||||
}
|
||||
|
||||
public static void notifyListeners(){
|
||||
for(WeakReference<MCOptionListener> weakReference : optionListeners){
|
||||
MCOptionListener optionListener = weakReference.get();
|
||||
if(optionListener == null) continue;
|
||||
|
||||
optionListener.onOptionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public static void addMCOptionListener(MCOptionListener listener){
|
||||
optionListeners.add(new WeakReference<>(listener));
|
||||
}
|
||||
|
||||
public static void removeMCOptionListener(MCOptionListener listener){
|
||||
for(WeakReference<MCOptionListener> weakReference : optionListeners){
|
||||
MCOptionListener optionListener = weakReference.get();
|
||||
if(optionListener == null) continue;
|
||||
if(optionListener == listener){
|
||||
optionListeners.remove(weakReference);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.AssetManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.tasks.MinecraftDownloaderTask;
|
||||
import net.kdt.pojavlaunch.value.PerVersionConfig;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class V117CompatUtil {
|
||||
/*
|
||||
/*
|
||||
New rendering engine was added in snapshot 21w10a
|
||||
21a08b (FP (GL2) engine): 20210225
|
||||
21w10a (non-FP engine): 20210310
|
||||
|
||||
boolean skipResDialog = false;
|
||||
if(mcReleaseDate > 20210225) skipResDialog = true;
|
||||
PerVersionConfig.update(); //Prepare the PVC
|
||||
PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(p1[0]); //Get the version config...
|
||||
if (cfg == null) {
|
||||
cfg = new PerVersionConfig.VersionConfig();//or create a new one!
|
||||
PerVersionConfig.configMap.put(p1[0], cfg);//and put it into the base
|
||||
}
|
||||
MCOptionUtils.load();
|
||||
|
||||
if(!skipResDialog) {
|
||||
AtomicBoolean proceed = new AtomicBoolean(false);
|
||||
Object lock = new Object();
|
||||
mActivity.runOnUiThread(() -> {
|
||||
AlertDialog.Builder bldr = new AlertDialog.Builder(mActivity);
|
||||
bldr.setTitle(R.string.global_warinng);
|
||||
bldr.setMessage(R.string.compat_117_message);
|
||||
bldr.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
proceed.set(true);
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setCancelable(false);
|
||||
bldr.show();
|
||||
});
|
||||
synchronized (lock) {
|
||||
lock.wait();
|
||||
}
|
||||
if(proceed.get()) {
|
||||
File resourcepacksDir = new File(cfg.gamePath==null? Tools.DIR_GAME_NEW:cfg.gamePath,"resourcepacks");
|
||||
if(!resourcepacksDir.exists()) resourcepacksDir.mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(new File(resourcepacksDir,"assets-v0.zip"));
|
||||
InputStream is = this.mActivity.getAssets().open("assets-v0.zip");
|
||||
IOUtils.copy(is,fos);
|
||||
is.close();fos.close();
|
||||
String resourcepacks = MCOptionUtils.get("resourcePacks");
|
||||
if(resourcepacks == null || !resourcepacks.contains("assets-v0.zip")) {
|
||||
List<String> resPacksArray = resourcepacks == null ? new ArrayList(): Arrays.asList()
|
||||
}
|
||||
}else throw new MinecraftDownloaderTask.SilentException();
|
||||
}
|
||||
*/
|
||||
private static List<String> getTexturePackList(String param) {
|
||||
if (param == null) {
|
||||
Log.i("V117CompatDebug","null, defaulting to empty");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Log.i("V117CompatDebug",param);
|
||||
if("[]".equals(param)) return new ArrayList<>();
|
||||
Log.i("V117CompatDebug","ph2");
|
||||
if(param == null) return new ArrayList<>();
|
||||
Log.i("V117CompatDebug","ph3");
|
||||
String rawList = param.substring(1,param.length()-1);
|
||||
Log.i("V117CompatDebug",rawList);
|
||||
return new ArrayList<>(Arrays.asList(rawList.split(",")));
|
||||
}
|
||||
private static String regenPackList(List<String> packs) {
|
||||
if(packs.size()==0) return "[]";
|
||||
String ret = "["+packs.get(0);
|
||||
for(int i = 1; i < packs.size(); i++) {
|
||||
ret += ","+packs.get(i);
|
||||
}
|
||||
ret += "]";
|
||||
return ret;
|
||||
}
|
||||
public static void runCheck(String version, Activity ctx) throws Exception{
|
||||
|
||||
PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(version);
|
||||
MCOptionUtils.load();
|
||||
|
||||
List<String> packList =getTexturePackList(MCOptionUtils.get("resourcePacks"));
|
||||
String renderer = cfg != null && cfg.renderer != null?cfg.renderer:LauncherPreferences.PREF_RENDERER;
|
||||
|
||||
if(renderer.equals("vulkan_zink") || renderer.equals("opengles3_virgl")) return; //don't install for zink/virgl users;
|
||||
if(packList.contains("\"assets-v0.zip\"") && renderer.equals("opengles3")) return;
|
||||
|
||||
Object lock = new Object();
|
||||
AtomicInteger proceed = new AtomicInteger(0);
|
||||
ctx.runOnUiThread(() -> {
|
||||
AlertDialog.Builder bldr = new AlertDialog.Builder(ctx);
|
||||
bldr.setTitle(R.string.global_warinng);
|
||||
bldr.setMessage(R.string.compat_117_message);
|
||||
bldr.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
proceed.set(1);
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setNeutralButton(R.string.compat_11x_playanyway, (dialog, which) -> {
|
||||
proceed.set(2);
|
||||
synchronized (lock) { lock.notifyAll(); }
|
||||
dialog.dismiss();
|
||||
});
|
||||
bldr.setCancelable(false);
|
||||
bldr.show();
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
lock.wait();
|
||||
}
|
||||
switch(proceed.get()) {
|
||||
case 1:
|
||||
if (cfg == null) {
|
||||
cfg = new PerVersionConfig.VersionConfig();
|
||||
PerVersionConfig.configMap.put(version, cfg);
|
||||
}
|
||||
cfg.renderer = "opengles3";
|
||||
String path = Tools.DIR_GAME_NEW;
|
||||
if(cfg.gamePath != null && !cfg.gamePath.isEmpty()) path = cfg.gamePath;
|
||||
copyResourcePack(path,ctx.getAssets());
|
||||
if(!packList.contains("\"assets-v0.zip\"")) packList.add(0,"\"assets-v0.zip\"");
|
||||
MCOptionUtils.set("resourcePacks",regenPackList(packList));
|
||||
MCOptionUtils.save();
|
||||
PerVersionConfig.update();
|
||||
break;
|
||||
case 0:
|
||||
throw new MinecraftDownloaderTask.SilentException();
|
||||
}
|
||||
}
|
||||
public static void copyResourcePack(String gameDir, AssetManager am) throws IOException {
|
||||
File resourcepacksDir = new File(gameDir,"resourcepacks");
|
||||
if(!resourcepacksDir.exists()) resourcepacksDir.mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(new File(resourcepacksDir,"assets-v0.zip"));
|
||||
InputStream is = am.open("assets-v0.zip");
|
||||
IOUtils.copy(is,fos);
|
||||
is.close();fos.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Base64;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.utils.DownloadUtils;
|
||||
|
||||
public class AccountSkin {
|
||||
public static Bitmap getSkin(String uuid) throws IOException {
|
||||
Profile p = Tools.GLOBAL_GSON.fromJson(DownloadUtils.downloadString("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid), Profile.class);
|
||||
for (Property property : p.properties) {
|
||||
if (property.name.equals("textures")) {
|
||||
return getSkinFromProperty(Tools.GLOBAL_GSON.fromJson(new String(Base64.decode(property.value, Base64.DEFAULT), "UTF-8"), SkinProperty.class));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Bitmap getSkinFromProperty(SkinProperty p) throws IOException {
|
||||
for (Map.Entry<String, Texture> texture : p.textures.entrySet()) {
|
||||
if (texture.getKey().equals("SKIN")) {
|
||||
String skinFile = File.createTempFile("skin", ".png", new File(Tools.DIR_DATA, "cache")).getAbsolutePath();
|
||||
Tools.downloadFile(texture.getValue().url.replace("http://","https://"), skinFile);
|
||||
return BitmapFactory.decodeFile(skinFile);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class Texture {
|
||||
public String url;
|
||||
}
|
||||
|
||||
public static class SkinProperty {
|
||||
public Map<String, Texture> textures;
|
||||
}
|
||||
|
||||
public static class Property {
|
||||
public String name, value;
|
||||
}
|
||||
|
||||
public static class Profile {
|
||||
public Property[] properties;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public class DependentLibrary {
|
||||
public String name;
|
||||
public LibraryDownloads downloads;
|
||||
public String url;
|
||||
|
||||
@Keep
|
||||
public static class LibraryDownloads {
|
||||
public MinecraftLibraryArtifact artifact;
|
||||
public LibraryDownloads(MinecraftLibraryArtifact artifact) {
|
||||
this.artifact = artifact;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class ForgeInstallProfile {
|
||||
// ----- < 1.12.2 Forge Install Profile -----
|
||||
public ForgeInstallProperties install;
|
||||
public JMinecraftVersionList.Version versionInfo;
|
||||
|
||||
public static class ForgeInstallProperties {
|
||||
public String profileName;
|
||||
public String target;
|
||||
public String path;
|
||||
public String version;
|
||||
public String filePath; // universal file .jar
|
||||
public String minecraft; // target Minecraft version
|
||||
}
|
||||
|
||||
// ----- 1.12.2+ Forge Install Profile -----
|
||||
public String version;
|
||||
public String json;
|
||||
public String path;
|
||||
public String minecraft; // target Minecraft version
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Log;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import java.io.*;
|
||||
import com.google.gson.*;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
public class MinecraftAccount
|
||||
{
|
||||
public String accessToken = "0"; // access token
|
||||
public String clientToken = "0"; // clientID: refresh and invalidate
|
||||
public String profileId = "00000000-0000-0000-0000-000000000000"; // profile UUID, for obtaining skin
|
||||
public String username = "Steve";
|
||||
public String selectedVersion = "1.7.10";
|
||||
public boolean isMicrosoft = false;
|
||||
public String msaRefreshToken = "0";
|
||||
public String skinFaceBase64;
|
||||
|
||||
void updateSkinFace(String uuid) {
|
||||
try {
|
||||
File skinFile = File.createTempFile("skin", ".png", new File(Tools.DIR_DATA, "cache"));
|
||||
Tools.downloadFile("https://mc-heads.net/head/" + uuid + "/100", skinFile.getAbsolutePath());
|
||||
skinFaceBase64 = Base64.encodeToString(IOUtils.toByteArray(new FileInputStream(skinFile)), Base64.DEFAULT);
|
||||
|
||||
Log.i("SkinLoader", "Update skin face success");
|
||||
} catch (IOException e) {
|
||||
// Skin refresh limit, no internet connection, etc...
|
||||
// Simply ignore updating skin face
|
||||
Log.w("SkinLoader", "Could not update skin face", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateSkinFace() {
|
||||
updateSkinFace(profileId);
|
||||
}
|
||||
|
||||
public String save(String outPath) throws IOException {
|
||||
Tools.write(outPath, Tools.GLOBAL_GSON.toJson(this));
|
||||
return username;
|
||||
}
|
||||
|
||||
public String save() throws IOException {
|
||||
return save(Tools.DIR_ACCOUNT_NEW + "/" + username + ".json");
|
||||
}
|
||||
|
||||
public static MinecraftAccount parse(String content) throws JsonSyntaxException {
|
||||
return Tools.GLOBAL_GSON.fromJson(content, MinecraftAccount.class);
|
||||
}
|
||||
|
||||
public static MinecraftAccount load(String name) throws JsonSyntaxException {
|
||||
if(!accountExists(name)) return new MinecraftAccount();
|
||||
try {
|
||||
MinecraftAccount acc = parse(Tools.read(Tools.DIR_ACCOUNT_NEW + "/" + name + ".json"));
|
||||
if (acc.accessToken == null) {
|
||||
acc.accessToken = "0";
|
||||
}
|
||||
if (acc.clientToken == null) {
|
||||
acc.clientToken = "0";
|
||||
}
|
||||
if (acc.profileId == null) {
|
||||
acc.profileId = "00000000-0000-0000-0000-000000000000";
|
||||
}
|
||||
if (acc.username == null) {
|
||||
acc.username = "0";
|
||||
}
|
||||
if (acc.selectedVersion == null) {
|
||||
acc.selectedVersion = "1.7.10";
|
||||
}
|
||||
if (acc.msaRefreshToken == null) {
|
||||
acc.msaRefreshToken = "0";
|
||||
}
|
||||
if (acc.skinFaceBase64 == null) {
|
||||
// acc.updateSkinFace("MHF_Steve");
|
||||
}
|
||||
return acc;
|
||||
} catch(IOException e) {
|
||||
Log.e(MinecraftAccount.class.getName(), "Caught an exception while loading the profile",e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap getSkinFace(){
|
||||
if(skinFaceBase64 == null){
|
||||
return Bitmap.createBitmap(1,1, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
byte[] faceIconBytes = Base64.decode(skinFaceBase64, Base64.DEFAULT);
|
||||
return BitmapFactory.decodeByteArray(faceIconBytes, 0, faceIconBytes.length);
|
||||
}
|
||||
|
||||
private static boolean accountExists(String username){
|
||||
return new File(Tools.DIR_ACCOUNT_NEW + "/" + username + ".json").exists();
|
||||
}
|
||||
|
||||
public static void clearTempAccount() {
|
||||
File tempAccFile = new File(Tools.DIR_DATA, "cache/tempacc.json");
|
||||
tempAccFile.delete();
|
||||
}
|
||||
|
||||
public static void saveTempAccount(MinecraftAccount acc) throws IOException {
|
||||
File tempAccFile = new File(Tools.DIR_DATA, "cache/tempacc.json");
|
||||
tempAccFile.delete();
|
||||
acc.save(tempAccFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
public class MinecraftClientInfo
|
||||
{
|
||||
public String sha1;
|
||||
public int size;
|
||||
public String url;
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
public class MinecraftLibraryArtifact extends MinecraftClientInfo
|
||||
{
|
||||
public String path;
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class PerVersionConfig {
|
||||
static File pvcFile;
|
||||
public static HashMap<String,VersionConfig> configMap;
|
||||
public static void update() throws IOException {
|
||||
if(configMap == null) {
|
||||
pvcFile = new File(Tools.DIR_GAME_HOME,"per-version-config.json");
|
||||
if(pvcFile.exists()) {
|
||||
configMap = Tools.GLOBAL_GSON.fromJson(Tools.read(pvcFile.getAbsolutePath()), new TypeToken<HashMap<String,VersionConfig>>() {}.getType());
|
||||
}else{
|
||||
configMap = new HashMap<>();
|
||||
}
|
||||
}else{
|
||||
Tools.write(pvcFile.getAbsolutePath(),Tools.GLOBAL_GSON.toJson(configMap));
|
||||
}
|
||||
}
|
||||
public static class VersionConfig {
|
||||
public String jvmArgs;
|
||||
public String gamePath;
|
||||
public String selectedRuntime;
|
||||
public String renderer;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import java.io.*;
|
||||
|
||||
public class LauncherProfiles
|
||||
{
|
||||
public static MinecraftLauncherProfiles mainProfileJson;
|
||||
public static File launcherProfilesFile = new File(Tools.DIR_GAME_NEW + "/launcher_profiles.json");
|
||||
public static MinecraftLauncherProfiles update() {
|
||||
try {
|
||||
if (mainProfileJson == null) {
|
||||
if (launcherProfilesFile.exists()) {
|
||||
mainProfileJson = Tools.GLOBAL_GSON.fromJson(Tools.read(launcherProfilesFile.getAbsolutePath()), MinecraftLauncherProfiles.class);
|
||||
} else {
|
||||
mainProfileJson = new MinecraftLauncherProfiles();
|
||||
}
|
||||
} else {
|
||||
Tools.write(launcherProfilesFile.getAbsolutePath(), mainProfileJson.toJson());
|
||||
}
|
||||
|
||||
// insertMissing();
|
||||
return mainProfileJson;
|
||||
} catch (Throwable th) {
|
||||
throw new RuntimeException(th);
|
||||
}
|
||||
}
|
||||
/*
|
||||
public static String insert;
|
||||
|
||||
private static void insertMissing() {
|
||||
if (mainProfileJson.authenticationDatabase == null) {
|
||||
MinecraftAuthenticationDatabase mad = new MinecraftAuthenticationDatabase();
|
||||
mainProfileJson.authenticationDatabase = mad;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
public class MinecraftAuthenticationDatabase
|
||||
{
|
||||
public String accessToken;
|
||||
public String displayName;
|
||||
public String username;
|
||||
public String uuid;
|
||||
// public MinecraftProfile[] profiles;
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class MinecraftLauncherProfiles
|
||||
{
|
||||
public Map<String, MinecraftProfile> profiles;
|
||||
public String clientToken;
|
||||
public Map<String, MinecraftAuthenticationDatabase> authenticationDatabase;
|
||||
// public Map launcherVersion;
|
||||
public MinecraftLauncherSettings settings;
|
||||
// public Map analyticsToken;
|
||||
public int analyticsFailcount;
|
||||
public MinecraftSelectedUser selectedUser;
|
||||
|
||||
public String toJson() {
|
||||
return Tools.GLOBAL_GSON.toJson(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
public class MinecraftLauncherSettings
|
||||
{
|
||||
public boolean enableSnapshots;
|
||||
public boolean enableAdvanced;
|
||||
public boolean keepLauncherOpen;
|
||||
public boolean showGameLog;
|
||||
public String locale;
|
||||
public boolean showMenu;
|
||||
public boolean enableHistorical;
|
||||
public String profileSorting;
|
||||
public boolean crashAssistance;
|
||||
public boolean enableAnalytics;
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
public class MinecraftProfile
|
||||
{
|
||||
public String name;
|
||||
public String type;
|
||||
public String created;
|
||||
public String lastUsed;
|
||||
public String icon;
|
||||
public String lastVersionId;
|
||||
public String gameDir;
|
||||
public String javaDir;
|
||||
public String javaArgs;
|
||||
public String logConfig;
|
||||
public boolean logConfigIsXML;
|
||||
public MinecraftResolution[] resolution;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
public class MinecraftResolution
|
||||
{
|
||||
public int width;
|
||||
public int height;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
public class MinecraftSelectedUser
|
||||
{
|
||||
public String account;
|
||||
public String profile;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue