mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2025-09-17 00:29:50 -04:00
Merge pull request #2006 from Mathias-Boulay/Control_revision_2.4
[V2.4] Controls revision
This commit is contained in:
commit
d7019746ed
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="net.kdt.pojavlaunch">
|
||||
|
||||
<uses-feature android:glEsVersion="0x00020000"/>
|
||||
@ -38,6 +39,25 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ImportControlActivity"
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="stateVisible"
|
||||
android:launchMode="singleInstance"
|
||||
android:configChanges="keyboard|keyboardHidden"
|
||||
>
|
||||
|
||||
<intent-filter android:scheme="content"
|
||||
android:label="@string/import_control_label"
|
||||
tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="application/json"/>
|
||||
<data android:mimeType="text/json"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/MenuDialog"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
|
@ -8,19 +8,16 @@ import static org.lwjgl.glfw.CallbackBridge.windowWidth;
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.*;
|
||||
import android.os.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.view.View.*;
|
||||
import android.view.inputmethod.*;
|
||||
import android.widget.*;
|
||||
|
||||
import androidx.drawerlayout.widget.*;
|
||||
import com.google.android.material.navigation.*;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.customcontrols.*;
|
||||
|
||||
@ -75,7 +72,8 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
}
|
||||
break;
|
||||
case MSG_DROP_ITEM_BUTTON_CHECK:
|
||||
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_Q, 0, true);
|
||||
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_Q);
|
||||
theHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 600);
|
||||
break;
|
||||
|
||||
}
|
||||
@ -99,6 +97,7 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
private ScrollView contentScroll;
|
||||
private ToggleButton toggleLog;
|
||||
private GestureDetector gestureDetector;
|
||||
private DoubleTapDetector doubleTapDetector;
|
||||
|
||||
private TextView debugText;
|
||||
private NavigationView.OnNavigationItemSelectedListener gameActionListener;
|
||||
@ -166,6 +165,8 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
|
||||
|
||||
gestureDetector = new GestureDetector(this, new SingleTapConfirm());
|
||||
doubleTapDetector = new DoubleTapDetector();
|
||||
|
||||
|
||||
// Menu
|
||||
drawerLayout = findViewById(R.id.main_drawer_options);
|
||||
@ -315,6 +316,11 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
|
||||
glTouchListener = new OnTouchListener(){
|
||||
private boolean isTouchInHotbar = false;
|
||||
private int lastHotbarKey = -1;
|
||||
/*
|
||||
* Tells if a double tap happened [MOUSE GRAB ONLY]. Doesn't tell where though.
|
||||
*/
|
||||
private boolean hasDoubleTapped = false;
|
||||
/*
|
||||
* Events can start with only a move instead of an pointerDown
|
||||
* It is due to the mouse passthrough option bundled with the control button.
|
||||
@ -344,15 +350,17 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
|
||||
//Getting scaled position from the event
|
||||
if(!CallbackBridge.isGrabbing()) {
|
||||
hasDoubleTapped = false;
|
||||
mouse_x = (e.getX() * scaleFactor);
|
||||
mouse_y = (e.getY() * scaleFactor);
|
||||
}
|
||||
|
||||
int hudKeyHandled;
|
||||
if (!CallbackBridge.isGrabbing() && gestureDetector.onTouchEvent(e)){
|
||||
//One android click = one MC click
|
||||
if(gestureDetector.onTouchEvent(e)){
|
||||
CallbackBridge.putMouseEventWithCoords(rightOverride ? (byte) 1 : (byte) 0, (int)mouse_x, (int)mouse_y);
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
hasDoubleTapped = doubleTapDetector.onTouchEvent(e);
|
||||
}
|
||||
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: // 0
|
||||
@ -364,14 +372,19 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
prevX = e.getX();
|
||||
prevY = e.getY();
|
||||
|
||||
|
||||
int hudKeyHandled;
|
||||
hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
|
||||
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(BaseMainActivity.MSG_DROP_ITEM_BUTTON_CHECK, LauncherPreferences.PREF_LONGPRESS_TRIGGER);
|
||||
theHandler.sendEmptyMessageDelayed(BaseMainActivity.MSG_DROP_ITEM_BUTTON_CHECK, 350);
|
||||
CallbackBridge.sendCursorPos(mouse_x, mouse_y);
|
||||
lastHotbarKey = hudKeyHandled;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -382,6 +395,7 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
initialY = mouse_y;
|
||||
if(!isTouchInHotbar) theHandler.sendEmptyMessageDelayed(BaseMainActivity.MSG_LEFT_MOUSE_BUTTON_CHECK, LauncherPreferences.PREF_LONGPRESS_TRIGGER);
|
||||
}
|
||||
lastHotbarKey = hudKeyHandled;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP: // 1
|
||||
@ -414,7 +428,15 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
scrollInitialY = CallbackBridge.mouseY;
|
||||
//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(hudKeyHandled != -1){
|
||||
sendKeyPress(hudKeyHandled);
|
||||
if(hasDoubleTapped && hudKeyHandled == lastHotbarKey){
|
||||
//Prevent double tapping Event on two different slots
|
||||
sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_F);
|
||||
}
|
||||
}
|
||||
|
||||
lastHotbarKey = hudKeyHandled;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
@ -470,14 +492,13 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
|
||||
@Override
|
||||
public boolean onCapturedPointer (View view, MotionEvent e) {
|
||||
if(e.getHistorySize() > 0) {
|
||||
mouse_x += (e.getX()*scaleFactor);
|
||||
mouse_y += (e.getY()*scaleFactor);
|
||||
}
|
||||
CallbackBridge.mouseX = (int) mouse_x;
|
||||
CallbackBridge.mouseY = (int) mouse_y;
|
||||
if(!CallbackBridge.isGrabbing()){
|
||||
view.releasePointerCapture();
|
||||
view.clearFocus();
|
||||
}
|
||||
|
||||
if (debugText.getVisibility() == View.VISIBLE && !debugErrored) {
|
||||
@ -636,9 +657,10 @@ public class BaseMainActivity extends LoggableActivity {
|
||||
|
||||
//Filtering useless events by order of probability
|
||||
if((event.getFlags() & KeyEvent.FLAG_FALLBACK) == KeyEvent.FLAG_FALLBACK) return true;
|
||||
if(event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) return true;
|
||||
if(event.getAction() == KeyEvent.KEYCODE_VOLUME_DOWN) return false;
|
||||
if(event.getAction() == KeyEvent.KEYCODE_VOLUME_UP) return false;
|
||||
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;
|
||||
|
||||
|
@ -0,0 +1,67 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
|
||||
|
||||
/**
|
||||
* Class aiming at better detecting double tap events for EVERY POINTER
|
||||
* Only uses the least amount of events possible,
|
||||
* since we aren't guaranteed to have all events in order
|
||||
*/
|
||||
public class DoubleTapDetector {
|
||||
|
||||
private final static int DOUBLE_TAP_MIN_DELTA_MS = 50;
|
||||
private final static int DOUBLE_TAP_MAX_DELTA_MS = 300;
|
||||
private final static int DOUBLE_TAP_SLOP_SQUARE_PX = (int) Math.pow(Tools.dpToPx(100), 2);
|
||||
|
||||
private long mLastEventTime = 0;
|
||||
private float mLastX = 9999;
|
||||
private float mLastY = 9999;
|
||||
|
||||
/**
|
||||
* A function to call when you have a touch event.
|
||||
* @param e The MotionEvent to inspect
|
||||
* @return whether or not a double tap happened for a pointer
|
||||
*/
|
||||
public boolean onTouchEvent(MotionEvent e){
|
||||
int eventAction = e.getActionMasked();
|
||||
int pointerIndex;
|
||||
|
||||
//Get the pointer index we want to look at
|
||||
if(eventAction == ACTION_DOWN) pointerIndex = 0;
|
||||
else if(eventAction == ACTION_POINTER_DOWN) pointerIndex = e.getActionIndex();
|
||||
else return false;
|
||||
|
||||
float eventX = e.getX(pointerIndex);
|
||||
float eventY = e.getY(pointerIndex);
|
||||
long eventTime = e.getEventTime();
|
||||
|
||||
long deltaTime = eventTime - mLastEventTime;
|
||||
if(deltaTime > DOUBLE_TAP_MIN_DELTA_MS && deltaTime < DOUBLE_TAP_MAX_DELTA_MS){
|
||||
int deltaX = (int) mLastX - (int) eventX;
|
||||
int deltaY = (int) mLastY - (int) eventY;
|
||||
if((deltaX*deltaX + deltaY*deltaY) < DOUBLE_TAP_SLOP_SQUARE_PX){
|
||||
//Then I guess there is a double tap :thonk:
|
||||
resetDoubleTapState();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
mLastEventTime = eventTime;
|
||||
mLastX = eventX;
|
||||
mLastY = eventY;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the double tap values.
|
||||
*/
|
||||
private void resetDoubleTapState(){
|
||||
mLastEventTime = 0;
|
||||
mLastX = 9999;
|
||||
mLastY = 9999;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -317,6 +317,7 @@ public class PojavLauncherActivity extends BaseLauncherActivity
|
||||
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);
|
||||
}
|
||||
|
@ -29,7 +29,9 @@ import org.lwjgl.glfw.*;
|
||||
import android.view.*;
|
||||
import android.widget.Toast;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.Build.VERSION_CODES.P;
|
||||
import static android.os.Build.VERSION_CODES.Q;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_IGNORE_NOTCH;
|
||||
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
|
||||
|
||||
@ -79,7 +81,7 @@ public final class Tools {
|
||||
public static void initContextConstants(Context ctx){
|
||||
DIR_DATA = ctx.getFilesDir().getParent();
|
||||
MULTIRT_HOME = DIR_DATA+"/runtimes";
|
||||
if(Build.VERSION.SDK_INT >= 29) {
|
||||
if(SDK_INT >= 29) {
|
||||
DIR_GAME_HOME = ctx.getExternalFilesDir(null).getAbsolutePath();
|
||||
}else{
|
||||
DIR_GAME_HOME = new File(Environment.getExternalStorageDirectory(),"games/PojavLauncher").getAbsolutePath();
|
||||
@ -338,11 +340,12 @@ public final class Tools {
|
||||
public static DisplayMetrics getDisplayMetrics(Activity ctx) {
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && (ctx.isInMultiWindowMode() || ctx.isInPictureInPictureMode())){
|
||||
if(SDK_INT >= Build.VERSION_CODES.N && (ctx.isInMultiWindowMode() || ctx.isInPictureInPictureMode())
|
||||
|| PREF_NOTCH_SIZE == -1 ){
|
||||
//For devices with free form/split screen, we need window size, not screen size.
|
||||
displayMetrics = ctx.getResources().getDisplayMetrics();
|
||||
}else{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (SDK_INT >= Build.VERSION_CODES.R) {
|
||||
ctx.getDisplay().getRealMetrics(displayMetrics);
|
||||
} else {
|
||||
ctx.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
|
||||
@ -828,7 +831,7 @@ public final class Tools {
|
||||
}
|
||||
|
||||
public static void ignoreNotch(boolean shouldIgnore, Activity ctx){
|
||||
if (Build.VERSION.SDK_INT >= P) {
|
||||
if (SDK_INT >= P) {
|
||||
if (shouldIgnore) {
|
||||
ctx.getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
} else {
|
||||
|
@ -1,6 +1,10 @@
|
||||
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;
|
||||
@ -29,6 +33,15 @@ public class ControlData {
|
||||
// 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
|
||||
@ -167,41 +180,16 @@ public class ControlData {
|
||||
|
||||
|
||||
public float insertDynamicPos(String dynamicPos) {
|
||||
// Values in the map below may be always changed
|
||||
Map<String, String> keyValueMap = new ArrayMap<>();
|
||||
keyValueMap.put("top", "0");
|
||||
keyValueMap.put("left", "0");
|
||||
keyValueMap.put("right", Float.toString(CallbackBridge.physicalWidth - getWidth()));
|
||||
keyValueMap.put("bottom", Float.toString(CallbackBridge.physicalHeight - getHeight()));
|
||||
keyValueMap.put("width", Float.toString(getWidth()));
|
||||
keyValueMap.put("height", Float.toString(getHeight()));
|
||||
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));
|
||||
|
||||
// Insert value to ${variable}
|
||||
String insertedPos = JSONUtils.insertSingleJSONValue(dynamicPos, keyValueMap);
|
||||
String insertedPos = JSONUtils.insertSingleJSONValue(dynamicPos, fillConversionMap());
|
||||
|
||||
// Calculate, because the dynamic position contains some math equations
|
||||
return calculate(insertedPos);
|
||||
}
|
||||
|
||||
private static float calculate(String math) {
|
||||
return (float) new ExpressionBuilder(math)
|
||||
.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]);
|
||||
}
|
||||
})
|
||||
.build().evaluate();
|
||||
setExpression(math);
|
||||
return (float) builder.get().build().evaluate();
|
||||
}
|
||||
|
||||
private static int[] inflateKeycodeArray(int[] keycodes){
|
||||
@ -236,4 +224,84 @@ public class ControlData {
|
||||
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 ignored){}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.D
|
||||
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.NonNull;
|
||||
|
||||
@ -21,37 +22,32 @@ public class ControlDrawerData {
|
||||
DOWN,
|
||||
LEFT,
|
||||
UP,
|
||||
RIGHT
|
||||
RIGHT,
|
||||
FREE
|
||||
}
|
||||
|
||||
public static Orientation[] getOrientations(){
|
||||
return new Orientation[]{DOWN,LEFT,UP,RIGHT};
|
||||
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 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 Orientation.DOWN;
|
||||
case 1:
|
||||
return Orientation.LEFT;
|
||||
case 2:
|
||||
return Orientation.UP;
|
||||
case 3:
|
||||
return RIGHT;
|
||||
case 0: return DOWN;
|
||||
case 1: return LEFT;
|
||||
case 2: return UP;
|
||||
case 3: return RIGHT;
|
||||
case 4: return FREE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -146,7 +146,10 @@ public class ControlLayout extends FrameLayout
|
||||
view.setAlpha(view.getProperties().opacity);
|
||||
view.setFocusable(false);
|
||||
view.setFocusableInTouchMode(false);
|
||||
}else{
|
||||
view.setVisible(drawer.areButtonsVisible);
|
||||
}
|
||||
|
||||
drawer.addButton(view);
|
||||
addView(view);
|
||||
|
||||
|
@ -51,13 +51,13 @@ public class CustomControls {
|
||||
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 = 3;
|
||||
version = 4;
|
||||
}
|
||||
|
||||
|
||||
public void save(String path) throws IOException {
|
||||
//Current version is the V2.3 so the version as to be marked as 3 !
|
||||
version = 3;
|
||||
//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));
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ public class LayoutConverter {
|
||||
CustomControls layout = LayoutConverter.convertV2Layout(layoutJobj);
|
||||
layout.save(jsonPath);
|
||||
return layout;
|
||||
}else if (layoutJobj.getInt("version") == 3) {
|
||||
}else if (layoutJobj.getInt("version") == 3 || layoutJobj.getInt("version") == 4) {
|
||||
return Tools.GLOBAL_GSON.fromJson(jsonLayoutData, CustomControls.class);
|
||||
}else{
|
||||
return null;
|
||||
|
@ -46,23 +46,18 @@ public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText
|
||||
@Override
|
||||
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
|
||||
super.onTextChanged(text, start, lengthBefore, lengthAfter);
|
||||
if(isDoingInternalChanges){
|
||||
isDoingInternalChanges = false;
|
||||
return;
|
||||
}
|
||||
if(lengthAfter < lengthBefore){
|
||||
for(int i=0; i< lengthBefore-lengthAfter; ++i){
|
||||
if(isDoingInternalChanges)return;
|
||||
|
||||
for(int i=0; i< lengthBefore; ++i){
|
||||
CallbackBridge.sendKeycode(LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE, '\u0008', 0, 0, true);
|
||||
}
|
||||
}else{
|
||||
for(int i=lengthBefore, index=lengthBefore+start; i < lengthAfter; ++i){
|
||||
//I didn't know F25 existed before that. I just need a full fat keycode for mc 1.13+
|
||||
CallbackBridge.sendKeycode(LWJGLGLFWKeycode.GLFW_KEY_F25, text.charAt(index), 0, 0, true);
|
||||
index ++;
|
||||
}
|
||||
for(int i=start, count = 0; count < lengthAfter; ++i){
|
||||
CallbackBridge.sendChar(text.charAt(i), 0);
|
||||
++count;
|
||||
}
|
||||
|
||||
clear();
|
||||
//Reset the keyboard state
|
||||
if(text.length() < 1) clear();
|
||||
}
|
||||
|
||||
|
||||
@ -88,10 +83,6 @@ public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText
|
||||
return super.onKeyPreIme(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(int index) {
|
||||
super.setSelection(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle on and off the soft keyboard, depending of the state
|
||||
@ -125,8 +116,9 @@ public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText
|
||||
isDoingInternalChanges = true;
|
||||
//Braille space, doesn't trigger keyboard auto-complete
|
||||
//replacing directly the text without though setText avoids notifying changes
|
||||
getText().replace(0, getText().length(),"\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800");
|
||||
setSelection(5);
|
||||
setText(" ");
|
||||
setSelection(getText().length());
|
||||
isDoingInternalChanges = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,7 +151,6 @@ public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This function deals with anything that has to be executed when the constructor is called
|
||||
*/
|
||||
|
@ -144,6 +144,12 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
|
||||
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) {
|
||||
super.setX(x);
|
||||
|
@ -59,6 +59,7 @@ public class ControlDrawer extends ControlButton {
|
||||
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:
|
||||
|
@ -9,6 +9,7 @@ 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 {
|
||||
@ -19,9 +20,7 @@ public class ControlSubButton extends ControlButton {
|
||||
super(layout, properties);
|
||||
this.parentDrawer = parentDrawer;
|
||||
|
||||
//Delayed to let the button inflate first
|
||||
if(!layout.getModifiable())
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> setVisibility(parentDrawer.areButtonsVisible ? VISIBLE : GONE), 0);
|
||||
|
||||
|
||||
filterProperties();
|
||||
}
|
||||
@ -50,7 +49,7 @@ public class ControlSubButton extends ControlButton {
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if(!mModifiable){
|
||||
if(!mModifiable || parentDrawer.drawerData.orientation == ControlDrawerData.Orientation.FREE){
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,6 @@ public class Gamepad {
|
||||
private float lastVerticalValue = 0.0f;
|
||||
|
||||
private final double mouseMaxAcceleration = 2f;
|
||||
private double acceleration = 0.0f;
|
||||
|
||||
private double mouseMagnitude;
|
||||
private double mouseAngle;
|
||||
@ -57,17 +56,32 @@ public class Gamepad {
|
||||
private GamepadMap currentMap = gameMap;
|
||||
|
||||
private boolean lastGrabbingState = true;
|
||||
private final boolean hasDigitalTriggers;
|
||||
private final boolean mModifierDigitalTriggers;
|
||||
private boolean mModifierSwappedAxis = true; //Triggers and right stick axis are swapped.
|
||||
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private final Handler inputHandler = new Handler(Looper.getMainLooper());
|
||||
private final Runnable switchStateRunnable;
|
||||
|
||||
public Gamepad(BaseMainActivity gameActivity, InputDevice inputDevice){
|
||||
//Toast.makeText(gameActivity.getApplicationContext(),"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);
|
||||
hasDigitalTriggers = inputDevice.hasKeys(KeyEvent.KEYCODE_BUTTON_R2)[0];
|
||||
else
|
||||
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_RX, MotionEvent.AXIS_RY, inputDevice);
|
||||
|
||||
mModifierDigitalTriggers = inputDevice.hasKeys(KeyEvent.KEYCODE_BUTTON_R2)[0];
|
||||
|
||||
this.gameActivity = gameActivity;
|
||||
pointerView = this.gameActivity.findViewById(R.id.console_pointer);
|
||||
@ -81,11 +95,11 @@ public class Gamepad {
|
||||
updateGrabbingState();
|
||||
tick();
|
||||
|
||||
handler.postDelayed(this, 16);
|
||||
inputHandler.postDelayed(this, 16);
|
||||
}
|
||||
};
|
||||
|
||||
handler.postDelayed(handlerRunnable, 16);
|
||||
inputHandler.postDelayed(handlerRunnable, 16);
|
||||
|
||||
//Initialize runnables to be used by the input system, avoiding generating one each time is better memory.
|
||||
switchStateRunnable = () -> {
|
||||
@ -93,7 +107,7 @@ public class Gamepad {
|
||||
if(lastGrabbingState){
|
||||
currentMap = gameMap;
|
||||
pointerView.setVisibility(View.INVISIBLE);
|
||||
mouseSensitivity = 22 / gameActivity.sensitivityFactor; //sensitivity in menus is resolution dependent.
|
||||
mouseSensitivity = 18;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -105,7 +119,8 @@ public class Gamepad {
|
||||
CallbackBridge.sendCursorPos(gameActivity.mouse_x, gameActivity.mouse_y);
|
||||
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
|
||||
pointerView.setVisibility(View.VISIBLE);
|
||||
mouseSensitivity = 14; //sensitivity in game doesn't need to be resolution dependent
|
||||
//sensitivity in menu is MC and HARDWARE resolution dependent
|
||||
mouseSensitivity = 19 * gameActivity.scaleFactor / gameActivity.sensitivityFactor;
|
||||
};
|
||||
|
||||
}
|
||||
@ -117,7 +132,7 @@ public class Gamepad {
|
||||
if(lastHorizontalValue != 0 || lastVerticalValue != 0){
|
||||
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
|
||||
|
||||
acceleration = (mouseMagnitude - currentJoystick.getDeadzone())/(1 - currentJoystick.getDeadzone());
|
||||
double acceleration = (mouseMagnitude - currentJoystick.getDeadzone()) / (1 - currentJoystick.getDeadzone());
|
||||
acceleration = Math.pow(acceleration, mouseMaxAcceleration);
|
||||
if(acceleration > 1) acceleration = 1;
|
||||
|
||||
@ -180,9 +195,15 @@ public class Gamepad {
|
||||
}
|
||||
|
||||
private void updateAnalogTriggers(MotionEvent event){
|
||||
if(!hasDigitalTriggers){
|
||||
getCurrentMap().TRIGGER_LEFT.update((event.getAxisValue(MotionEvent.AXIS_LTRIGGER) > 0.5) || (event.getAxisValue(MotionEvent.AXIS_BRAKE) > 0.5));
|
||||
getCurrentMap().TRIGGER_RIGHT.update((event.getAxisValue(MotionEvent.AXIS_RTRIGGER) > 0.5) || (event.getAxisValue(MotionEvent.AXIS_GAS) > 0.5));
|
||||
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) );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ public class GamepadJoystick {
|
||||
public static final int DIRECTION_SOUTH = 6;
|
||||
public static final int DIRECTION_SOUTH_EAST = 7;
|
||||
|
||||
private float deadzone;
|
||||
private final InputDevice device;
|
||||
|
||||
private final int verticalAxis;
|
||||
private final int horizontalAxis;
|
||||
@ -28,13 +28,9 @@ public class GamepadJoystick {
|
||||
public GamepadJoystick(int horizontalAxis, int verticalAxis, InputDevice device){
|
||||
this.verticalAxis = verticalAxis;
|
||||
this.horizontalAxis = horizontalAxis;
|
||||
this.device = device;
|
||||
|
||||
//Some controllers aren't recognized as such by android, so we fallback to a default value of 0.2
|
||||
//And some others don't report their MotionRange. This was the case with the xbox one series S controller.
|
||||
|
||||
//try { deadzone = Math.max(device.getMotionRange(verticalAxis).getFlat(), device.getMotionRange(horizontalAxis).getFlat()) * 1.9f; }
|
||||
//catch (NullPointerException e){ deadzone = 0.2f; }
|
||||
deadzone = 0.2f;
|
||||
}
|
||||
|
||||
public double getAngleRadian(MotionEvent event){
|
||||
@ -71,6 +67,7 @@ public class GamepadJoystick {
|
||||
//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)) );
|
||||
@ -83,12 +80,20 @@ public class GamepadJoystick {
|
||||
|
||||
|
||||
public int getHeightDirection(MotionEvent event){
|
||||
if(getMagnitude(event) <= deadzone) return DIRECTION_NONE;
|
||||
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() {
|
||||
return deadzone;
|
||||
try{
|
||||
return Math.max(device.getMotionRange(horizontalAxis).getFlat() * 1.9f, 0.2f);
|
||||
}catch (Exception e){
|
||||
return 0.2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
|
||||
private TextView mDeleteTextView;
|
||||
private TextView mCloneTextView;
|
||||
|
||||
private ControlButton editedButton;
|
||||
private final ControlButton editedButton;
|
||||
|
||||
public ActionPopupWindow(HandleView handleView, ControlButton button){
|
||||
super(handleView);
|
||||
@ -145,6 +145,11 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
|
||||
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}";
|
||||
|
@ -1,5 +1,9 @@
|
||||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
public class FileUtils
|
||||
{
|
||||
import java.io.File;
|
||||
|
||||
public class FileUtils {
|
||||
public static boolean exists(String filePath){
|
||||
return new File(filePath).exists();
|
||||
}
|
||||
}
|
@ -79,6 +79,11 @@ public class CallbackBridge {
|
||||
// sendData(JRE_TYPE_KEYCODE_CONTROL, keycode, Character.toString(keychar), Boolean.toString(isDown), modifiers);
|
||||
}
|
||||
|
||||
public static void sendChar(char keychar, int modifiers){
|
||||
nativeSendCharMods(keychar,modifiers);
|
||||
nativeSendChar(keychar);
|
||||
}
|
||||
|
||||
public static void sendMouseKeycode(int button, int modifiers, boolean isDown) {
|
||||
DEBUG_STRING.append("MouseKey=").append(button).append(", down=").append(isDown).append("\n");
|
||||
// if (isGrabbing()) DEBUG_STRING.append("MouseGrabStrace: " + android.util.Log.getStackTraceString(new Throwable()) + "\n");
|
||||
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
<corners
|
||||
android:radius="5dp"/>
|
||||
<solid
|
||||
android:color="#272727" />
|
||||
</shape>
|
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="-10dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/sign_in_background" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:background="@drawable/menu_background"
|
||||
android:paddingVertical="20dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.3"
|
||||
tools:layout_editor_absoluteX="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_import_control_file_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:text="File name:"
|
||||
app:layout_constraintBottom_toTopOf="@+id/editText_import_control_file_name"
|
||||
app:layout_constraintStart_toStartOf="@+id/editText_import_control_file_name" />
|
||||
|
||||
<com.kdt.mcgui.MineEditText
|
||||
android:id="@+id/editText_import_control_file_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="35dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:hint="File name"
|
||||
android:imeOptions="flagForceAscii"
|
||||
|
||||
app:layout_constraintBottom_toTopOf="@+id/mineButton_import_control" />
|
||||
|
||||
<com.kdt.mcgui.MineButton
|
||||
android:id="@+id/mineButton_import_control"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
|
||||
android:background="@drawable/mine_button_background"
|
||||
android:onClick="startImport"
|
||||
|
||||
android:text="@string/import_control_import_button"
|
||||
android:textColor="@android:color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/editText_import_control_file_name"
|
||||
|
||||
app:layout_constraintStart_toStartOf="@+id/editText_import_control_file_name"
|
||||
|
||||
app:layout_constraintVertical_bias="0.70" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
@ -27,6 +27,11 @@
|
||||
android:id="@+id/login_spinner_language"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/menu_background"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="3dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
@ -47,7 +52,7 @@
|
||||
app:layout_constraintWidth_max="480dp"
|
||||
android:layout_height="300dp"
|
||||
|
||||
android:background="#272727"
|
||||
android:background="@drawable/menu_background"
|
||||
android:translationZ="-1dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -53,8 +53,8 @@
|
||||
android:layout_height="1dp"
|
||||
android:background="@android:color/darker_gray"
|
||||
android:ems="10"
|
||||
android:imeOptions="flagNoFullscreen|flagNoExtractUi|flagNoPersonalizedLearning|actionSend"
|
||||
android:inputType="textVisiblePassword"
|
||||
android:imeOptions="flagNoFullscreen|flagNoExtractUi|flagNoPersonalizedLearning|actionDone"
|
||||
android:inputType="textFilter|textImeMultiLine|textAutoComplete|textAutoCorrect"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
</net.kdt.pojavlaunch.customcontrols.ControlLayout>
|
||||
|
@ -160,6 +160,14 @@
|
||||
<string name="control_customkey">Send custom keycode</string>
|
||||
<string name="control_scaleup">Scale up</string>
|
||||
<string name="control_scaledown">Scale down</string>
|
||||
|
||||
<!-- ImportControlActivity Strings -->
|
||||
<string name="import_control_label">Import controls</string>
|
||||
<string name="import_control_invalid_file">Invalid or corrupted file</string>
|
||||
<string name="import_control_import_button">import controls</string>
|
||||
<string name="import_control_verifying_file">File is being verified, please wait and try again…</string>
|
||||
<string name="import_control_invalid_name">Invalid name or file already exists</string>
|
||||
<string name="import_control_done">Importation done</string>
|
||||
<!--
|
||||
<string name="control_more3"></string>
|
||||
<string name="control_more4"></string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user