Merge pull request #2006 from Mathias-Boulay/Control_revision_2.4

[V2.4] Controls revision
This commit is contained in:
Boulay Mathias 2021-09-13 19:52:52 +02:00 committed by GitHub
commit d7019746ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 686 additions and 171 deletions

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="net.kdt.pojavlaunch"> package="net.kdt.pojavlaunch">
<uses-feature android:glEsVersion="0x00020000"/> <uses-feature android:glEsVersion="0x00020000"/>
@ -38,6 +39,25 @@
</intent-filter> </intent-filter>
</activity> </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 <activity
android:theme="@style/MenuDialog" android:theme="@style/MenuDialog"
android:screenOrientation="sensorLandscape" android:screenOrientation="sensorLandscape"

View File

@ -8,19 +8,16 @@ import static org.lwjgl.glfw.CallbackBridge.windowWidth;
import android.app.*; import android.app.*;
import android.content.*; import android.content.*;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.*; import android.graphics.*;
import android.os.*; import android.os.*;
import android.util.*; import android.util.*;
import android.view.*; import android.view.*;
import android.view.View.*; import android.view.View.*;
import android.view.inputmethod.*;
import android.widget.*; import android.widget.*;
import androidx.drawerlayout.widget.*; import androidx.drawerlayout.widget.*;
import com.google.android.material.navigation.*; import com.google.android.material.navigation.*;
import java.io.*; import java.io.*;
import java.lang.reflect.*;
import java.util.*; import java.util.*;
import net.kdt.pojavlaunch.customcontrols.*; import net.kdt.pojavlaunch.customcontrols.*;
@ -75,7 +72,8 @@ public class BaseMainActivity extends LoggableActivity {
} }
break; break;
case MSG_DROP_ITEM_BUTTON_CHECK: 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; break;
} }
@ -99,6 +97,7 @@ public class BaseMainActivity extends LoggableActivity {
private ScrollView contentScroll; private ScrollView contentScroll;
private ToggleButton toggleLog; private ToggleButton toggleLog;
private GestureDetector gestureDetector; private GestureDetector gestureDetector;
private DoubleTapDetector doubleTapDetector;
private TextView debugText; private TextView debugText;
private NavigationView.OnNavigationItemSelectedListener gameActionListener; private NavigationView.OnNavigationItemSelectedListener gameActionListener;
@ -166,6 +165,8 @@ public class BaseMainActivity extends LoggableActivity {
gestureDetector = new GestureDetector(this, new SingleTapConfirm()); gestureDetector = new GestureDetector(this, new SingleTapConfirm());
doubleTapDetector = new DoubleTapDetector();
// Menu // Menu
drawerLayout = findViewById(R.id.main_drawer_options); drawerLayout = findViewById(R.id.main_drawer_options);
@ -315,6 +316,11 @@ public class BaseMainActivity extends LoggableActivity {
glTouchListener = new OnTouchListener(){ glTouchListener = new OnTouchListener(){
private boolean isTouchInHotbar = false; 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 * Events can start with only a move instead of an pointerDown
* It is due to the mouse passthrough option bundled with the control button. * It is due to the mouse passthrough option bundled with the control button.
@ -344,14 +350,16 @@ public class BaseMainActivity extends LoggableActivity {
//Getting scaled position from the event //Getting scaled position from the event
if(!CallbackBridge.isGrabbing()) { if(!CallbackBridge.isGrabbing()) {
hasDoubleTapped = false;
mouse_x = (e.getX() * scaleFactor); mouse_x = (e.getX() * scaleFactor);
mouse_y = (e.getY() * scaleFactor); mouse_y = (e.getY() * scaleFactor);
} //One android click = one MC click
if(gestureDetector.onTouchEvent(e)){
int hudKeyHandled; CallbackBridge.putMouseEventWithCoords(rightOverride ? (byte) 1 : (byte) 0, (int)mouse_x, (int)mouse_y);
if (!CallbackBridge.isGrabbing() && gestureDetector.onTouchEvent(e)){ return true;
CallbackBridge.putMouseEventWithCoords(rightOverride ? (byte) 1 : (byte) 0, (int)mouse_x, (int)mouse_y); }
return true; }else{
hasDoubleTapped = doubleTapDetector.onTouchEvent(e);
} }
switch (e.getActionMasked()) { switch (e.getActionMasked()) {
@ -364,14 +372,19 @@ public class BaseMainActivity extends LoggableActivity {
prevX = e.getX(); prevX = e.getX();
prevY = e.getY(); prevY = e.getY();
int hudKeyHandled;
hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY()); hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
isTouchInHotbar = hudKeyHandled != -1; isTouchInHotbar = hudKeyHandled != -1;
if (isTouchInHotbar) { if (isTouchInHotbar) {
sendKeyPress(hudKeyHandled); 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); CallbackBridge.sendCursorPos(mouse_x, mouse_y);
lastHotbarKey = hudKeyHandled;
break; break;
} }
@ -382,6 +395,7 @@ public class BaseMainActivity extends LoggableActivity {
initialY = mouse_y; initialY = mouse_y;
if(!isTouchInHotbar) theHandler.sendEmptyMessageDelayed(BaseMainActivity.MSG_LEFT_MOUSE_BUTTON_CHECK, LauncherPreferences.PREF_LONGPRESS_TRIGGER); if(!isTouchInHotbar) theHandler.sendEmptyMessageDelayed(BaseMainActivity.MSG_LEFT_MOUSE_BUTTON_CHECK, LauncherPreferences.PREF_LONGPRESS_TRIGGER);
} }
lastHotbarKey = hudKeyHandled;
break; break;
case MotionEvent.ACTION_UP: // 1 case MotionEvent.ACTION_UP: // 1
@ -414,7 +428,15 @@ public class BaseMainActivity extends LoggableActivity {
scrollInitialY = CallbackBridge.mouseY; scrollInitialY = CallbackBridge.mouseY;
//Checking if we are pressing the hotbar to select the item //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)); 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; break;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
@ -470,58 +492,57 @@ public class BaseMainActivity extends LoggableActivity {
@Override @Override
public boolean onCapturedPointer (View view, MotionEvent e) { public boolean onCapturedPointer (View view, MotionEvent e) {
if(e.getHistorySize() > 0) { mouse_x += (e.getX()*scaleFactor);
mouse_x += (e.getX()*scaleFactor); mouse_y += (e.getY()*scaleFactor);
mouse_y += (e.getY()*scaleFactor); CallbackBridge.mouseX = (int) mouse_x;
} CallbackBridge.mouseY = (int) mouse_y;
CallbackBridge.mouseX = (int) mouse_x; if(!CallbackBridge.isGrabbing()){
CallbackBridge.mouseY = (int) mouse_y; view.releasePointerCapture();
if(!CallbackBridge.isGrabbing()){ view.clearFocus();
view.releasePointerCapture(); }
}
if (debugText.getVisibility() == View.VISIBLE && !debugErrored) { if (debugText.getVisibility() == View.VISIBLE && !debugErrored) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
try { try {
builder.append("PointerCapture debug\n"); builder.append("PointerCapture debug\n");
builder.append("MotionEvent=").append(e.getActionMasked()).append("\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("PressingBtn=").append(MotionEvent.class.getDeclaredMethod("buttonStateToString").invoke(null, e.getButtonState())).append("\n\n");
builder.append("PointerX=").append(e.getX()).append("\n"); builder.append("PointerX=").append(e.getX()).append("\n");
builder.append("PointerY=").append(e.getY()).append("\n"); builder.append("PointerY=").append(e.getY()).append("\n");
builder.append("RawX=").append(e.getRawX()).append("\n"); builder.append("RawX=").append(e.getRawX()).append("\n");
builder.append("RawY=").append(e.getRawY()).append("\n\n"); builder.append("RawY=").append(e.getRawY()).append("\n\n");
builder.append("XPos=").append(mouse_x).append("\n"); builder.append("XPos=").append(mouse_x).append("\n");
builder.append("YPos=").append(mouse_y).append("\n\n"); builder.append("YPos=").append(mouse_y).append("\n\n");
builder.append("MovingX=").append(getMoving(e.getX(), true)).append("\n"); builder.append("MovingX=").append(getMoving(e.getX(), true)).append("\n");
builder.append("MovingY=").append(getMoving(e.getY(), false)).append("\n"); builder.append("MovingY=").append(getMoving(e.getY(), false)).append("\n");
} catch (Throwable th) { } catch (Throwable th) {
debugErrored = true; debugErrored = true;
builder.append("Error getting debug. The debug will be stopped!\n").append(Log.getStackTraceString(th)); builder.append("Error getting debug. The debug will be stopped!\n").append(Log.getStackTraceString(th));
} finally { } finally {
debugText.setText(builder.toString()); debugText.setText(builder.toString());
builder.setLength(0); builder.setLength(0);
}
}
debugText.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;
} }
} }
}); debugText.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;
}
}
});
} }
minecraftGLView.setOnTouchListener(glTouchListener); minecraftGLView.setOnTouchListener(glTouchListener);
minecraftGLView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener(){ minecraftGLView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener(){
@ -636,9 +657,10 @@ public class BaseMainActivity extends LoggableActivity {
//Filtering useless events by order of probability //Filtering useless events by order of probability
if((event.getFlags() & KeyEvent.FLAG_FALLBACK) == KeyEvent.FLAG_FALLBACK) return true; if((event.getFlags() & KeyEvent.FLAG_FALLBACK) == KeyEvent.FLAG_FALLBACK) return true;
if(event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) return true; int eventKeycode = event.getKeyCode();
if(event.getAction() == KeyEvent.KEYCODE_VOLUME_DOWN) return false; if(eventKeycode == KeyEvent.KEYCODE_UNKNOWN) return true;
if(event.getAction() == KeyEvent.KEYCODE_VOLUME_UP) return false; if(eventKeycode == KeyEvent.KEYCODE_VOLUME_DOWN) return false;
if(eventKeycode == KeyEvent.KEYCODE_VOLUME_UP) return false;
if(event.getRepeatCount() != 0) return true; if(event.getRepeatCount() != 0) return true;
if(event.getAction() == KeyEvent.ACTION_MULTIPLE) return true; if(event.getAction() == KeyEvent.ACTION_MULTIPLE) return true;

View File

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

View File

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

View File

@ -317,6 +317,7 @@ public class PojavLauncherActivity extends BaseLauncherActivity
PREF_NOTCH_SIZE = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout().getBoundingRects().get(0).width(); PREF_NOTCH_SIZE = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout().getBoundingRects().get(0).width();
}catch (Exception e){ }catch (Exception e){
Log.i("NOTCH DETECTION", "No notch detected, or the device if in split screen mode"); Log.i("NOTCH DETECTION", "No notch detected, or the device if in split screen mode");
PREF_NOTCH_SIZE = -1;
} }
Tools.updateWindowSize(this); Tools.updateWindowSize(this);
} }

View File

@ -29,7 +29,9 @@ import org.lwjgl.glfw.*;
import android.view.*; import android.view.*;
import android.widget.Toast; 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.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_IGNORE_NOTCH;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
@ -79,7 +81,7 @@ public final class Tools {
public static void initContextConstants(Context ctx){ public static void initContextConstants(Context ctx){
DIR_DATA = ctx.getFilesDir().getParent(); DIR_DATA = ctx.getFilesDir().getParent();
MULTIRT_HOME = DIR_DATA+"/runtimes"; MULTIRT_HOME = DIR_DATA+"/runtimes";
if(Build.VERSION.SDK_INT >= 29) { if(SDK_INT >= 29) {
DIR_GAME_HOME = ctx.getExternalFilesDir(null).getAbsolutePath(); DIR_GAME_HOME = ctx.getExternalFilesDir(null).getAbsolutePath();
}else{ }else{
DIR_GAME_HOME = new File(Environment.getExternalStorageDirectory(),"games/PojavLauncher").getAbsolutePath(); DIR_GAME_HOME = new File(Environment.getExternalStorageDirectory(),"games/PojavLauncher").getAbsolutePath();
@ -338,11 +340,12 @@ public final class Tools {
public static DisplayMetrics getDisplayMetrics(Activity ctx) { public static DisplayMetrics getDisplayMetrics(Activity ctx) {
DisplayMetrics displayMetrics = new DisplayMetrics(); 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. //For devices with free form/split screen, we need window size, not screen size.
displayMetrics = ctx.getResources().getDisplayMetrics(); displayMetrics = ctx.getResources().getDisplayMetrics();
}else{ }else{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (SDK_INT >= Build.VERSION_CODES.R) {
ctx.getDisplay().getRealMetrics(displayMetrics); ctx.getDisplay().getRealMetrics(displayMetrics);
} else { } else {
ctx.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics); ctx.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
@ -828,7 +831,7 @@ public final class Tools {
} }
public static void ignoreNotch(boolean shouldIgnore, Activity ctx){ public static void ignoreNotch(boolean shouldIgnore, Activity ctx){
if (Build.VERSION.SDK_INT >= P) { if (SDK_INT >= P) {
if (shouldIgnore) { if (shouldIgnore) {
ctx.getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; ctx.getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
} else { } else {

View File

@ -1,6 +1,10 @@
package net.kdt.pojavlaunch.customcontrols; package net.kdt.pojavlaunch.customcontrols;
import android.util.*; import android.util.*;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*; import java.util.*;
import net.kdt.pojavlaunch.*; import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.prefs.LauncherPreferences; import net.kdt.pojavlaunch.prefs.LauncherPreferences;
@ -28,7 +32,16 @@ public class ControlData {
// Internal usage only // Internal usage only
public boolean isHideable; 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 * Both fields below are dynamic position data, auto updates
* X and Y position, unlike the original one which uses fixed * X and Y position, unlike the original one which uses fixed
@ -167,41 +180,16 @@ public class ControlData {
public float insertDynamicPos(String dynamicPos) { 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} // 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 // Calculate, because the dynamic position contains some math equations
return calculate(insertedPos); return calculate(insertedPos);
} }
private static float calculate(String math) { private static float calculate(String math) {
return (float) new ExpressionBuilder(math) setExpression(math);
.function(new Function("dp", 1) { return (float) builder.get().build().evaluate();
@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();
} }
private static int[] inflateKeycodeArray(int[] keycodes){ private static int[] inflateKeycodeArray(int[] keycodes){
@ -236,4 +224,84 @@ public class ControlData {
public void setHeight(float heightInPx){ public void setHeight(float heightInPx){
height = Tools.pxToDp(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;
}
} }

View File

@ -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.LEFT;
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.RIGHT; 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.UP;
import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.FREE;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -21,37 +22,32 @@ public class ControlDrawerData {
DOWN, DOWN,
LEFT, LEFT,
UP, UP,
RIGHT RIGHT,
FREE
} }
public static Orientation[] getOrientations(){ 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){ public static int orientationToInt(Orientation orientation){
switch (orientation){ switch (orientation){
case DOWN: case DOWN: return 0;
return 0; case LEFT: return 1;
case LEFT: case UP: return 2;
return 1; case RIGHT: return 3;
case UP: case FREE: return 4;
return 2;
case RIGHT:
return 3;
} }
return -1; return -1;
} }
public static Orientation intToOrientation(int by){ public static Orientation intToOrientation(int by){
switch (by){ switch (by){
case 0: case 0: return DOWN;
return Orientation.DOWN; case 1: return LEFT;
case 1: case 2: return UP;
return Orientation.LEFT; case 3: return RIGHT;
case 2: case 4: return FREE;
return Orientation.UP;
case 3:
return RIGHT;
} }
return null; return null;
} }

View File

@ -146,7 +146,10 @@ public class ControlLayout extends FrameLayout
view.setAlpha(view.getProperties().opacity); view.setAlpha(view.getProperties().opacity);
view.setFocusable(false); view.setFocusable(false);
view.setFocusableInTouchMode(false); view.setFocusableInTouchMode(false);
}else{
view.setVisible(drawer.areButtonsVisible);
} }
drawer.addButton(view); drawer.addButton(view);
addView(view); addView(view);

View File

@ -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)); 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 //The default controls are conform to the V2
version = 3; version = 4;
} }
public void save(String path) throws IOException { public void save(String path) throws IOException {
//Current version is the V2.3 so the version as to be marked as 3 ! //Current version is the V2.4 so the version as to be marked as 4 !
version = 3; version = 4;
Tools.write(path, Tools.GLOBAL_GSON.toJson(this)); Tools.write(path, Tools.GLOBAL_GSON.toJson(this));
} }

View File

@ -28,7 +28,7 @@ public class LayoutConverter {
CustomControls layout = LayoutConverter.convertV2Layout(layoutJobj); CustomControls layout = LayoutConverter.convertV2Layout(layoutJobj);
layout.save(jsonPath); layout.save(jsonPath);
return layout; 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); return Tools.GLOBAL_GSON.fromJson(jsonLayoutData, CustomControls.class);
}else{ }else{
return null; return null;

View File

@ -46,23 +46,18 @@ public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText
@Override @Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter); super.onTextChanged(text, start, lengthBefore, lengthAfter);
if(isDoingInternalChanges){ if(isDoingInternalChanges)return;
isDoingInternalChanges = false;
return; for(int i=0; i< lengthBefore; ++i){
CallbackBridge.sendKeycode(LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE, '\u0008', 0, 0, true);
} }
if(lengthAfter < lengthBefore){ for(int i=start, count = 0; count < lengthAfter; ++i){
for(int i=0; i< lengthBefore-lengthAfter; ++i){ CallbackBridge.sendChar(text.charAt(i), 0);
CallbackBridge.sendKeycode(LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE, '\u0008', 0, 0, true); ++count;
}
}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 ++;
}
} }
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); 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 * 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; isDoingInternalChanges = true;
//Braille space, doesn't trigger keyboard auto-complete //Braille space, doesn't trigger keyboard auto-complete
//replacing directly the text without though setText avoids notifying changes //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"); setText(" ");
setSelection(5); 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 * This function deals with anything that has to be executed when the constructor is called
*/ */

View File

@ -144,6 +144,12 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
setVisibility(isVisible ? VISIBLE : GONE); setVisibility(isVisible ? VISIBLE : GONE);
} }
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
setWillNotDraw(visibility == GONE);
}
@Override @Override
public void setX(float x) { public void setX(float x) {
super.setX(x); super.setX(x);

View File

@ -59,6 +59,7 @@ public class ControlDrawer extends ControlButton {
private void alignButtons(){ private void alignButtons(){
if(buttons == null) return; if(buttons == null) return;
if(drawerData.orientation == ControlDrawerData.Orientation.FREE) return;
for(int i=0; i < buttons.size(); ++i){ for(int i=0; i < buttons.size(); ++i){
switch (drawerData.orientation){ switch (drawerData.orientation){
case RIGHT: case RIGHT:

View File

@ -9,6 +9,7 @@ import android.view.ViewGroup;
import net.kdt.pojavlaunch.SingleTapConfirm; import net.kdt.pojavlaunch.SingleTapConfirm;
import net.kdt.pojavlaunch.customcontrols.ControlData; import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout; import net.kdt.pojavlaunch.customcontrols.ControlLayout;
public class ControlSubButton extends ControlButton { public class ControlSubButton extends ControlButton {
@ -19,9 +20,7 @@ public class ControlSubButton extends ControlButton {
super(layout, properties); super(layout, properties);
this.parentDrawer = parentDrawer; 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(); filterProperties();
} }
@ -50,7 +49,7 @@ public class ControlSubButton extends ControlButton {
@Override @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(MotionEvent event) {
if(!mModifiable){ if(!mModifiable || parentDrawer.drawerData.orientation == ControlDrawerData.Orientation.FREE){
return super.onTouchEvent(event); return super.onTouchEvent(event);
} }

View File

@ -46,7 +46,6 @@ public class Gamepad {
private float lastVerticalValue = 0.0f; private float lastVerticalValue = 0.0f;
private final double mouseMaxAcceleration = 2f; private final double mouseMaxAcceleration = 2f;
private double acceleration = 0.0f;
private double mouseMagnitude; private double mouseMagnitude;
private double mouseAngle; private double mouseAngle;
@ -57,17 +56,32 @@ public class Gamepad {
private GamepadMap currentMap = gameMap; private GamepadMap currentMap = gameMap;
private boolean lastGrabbingState = true; 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; private final Runnable switchStateRunnable;
public Gamepad(BaseMainActivity gameActivity, InputDevice inputDevice){ public Gamepad(BaseMainActivity gameActivity, InputDevice inputDevice){
//Toast.makeText(gameActivity.getApplicationContext(),"GAMEPAD CREATED", Toast.LENGTH_LONG).show(); //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); leftJoystick = new GamepadJoystick(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, inputDevice);
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, inputDevice); if(!mModifierSwappedAxis)
hasDigitalTriggers = inputDevice.hasKeys(KeyEvent.KEYCODE_BUTTON_R2)[0]; 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];
this.gameActivity = gameActivity; this.gameActivity = gameActivity;
pointerView = this.gameActivity.findViewById(R.id.console_pointer); pointerView = this.gameActivity.findViewById(R.id.console_pointer);
@ -81,11 +95,11 @@ public class Gamepad {
updateGrabbingState(); updateGrabbingState();
tick(); 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. //Initialize runnables to be used by the input system, avoiding generating one each time is better memory.
switchStateRunnable = () -> { switchStateRunnable = () -> {
@ -93,7 +107,7 @@ public class Gamepad {
if(lastGrabbingState){ if(lastGrabbingState){
currentMap = gameMap; currentMap = gameMap;
pointerView.setVisibility(View.INVISIBLE); pointerView.setVisibility(View.INVISIBLE);
mouseSensitivity = 22 / gameActivity.sensitivityFactor; //sensitivity in menus is resolution dependent. mouseSensitivity = 18;
return; return;
} }
@ -105,7 +119,8 @@ public class Gamepad {
CallbackBridge.sendCursorPos(gameActivity.mouse_x, gameActivity.mouse_y); CallbackBridge.sendCursorPos(gameActivity.mouse_x, gameActivity.mouse_y);
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2); placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
pointerView.setVisibility(View.VISIBLE); 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){ if(lastHorizontalValue != 0 || lastVerticalValue != 0){
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick; 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); acceleration = Math.pow(acceleration, mouseMaxAcceleration);
if(acceleration > 1) acceleration = 1; if(acceleration > 1) acceleration = 1;
@ -180,9 +195,15 @@ public class Gamepad {
} }
private void updateAnalogTriggers(MotionEvent event){ private void updateAnalogTriggers(MotionEvent event){
if(!hasDigitalTriggers){ if(!mModifierDigitalTriggers){
getCurrentMap().TRIGGER_LEFT.update((event.getAxisValue(MotionEvent.AXIS_LTRIGGER) > 0.5) || (event.getAxisValue(MotionEvent.AXIS_BRAKE) > 0.5)); getCurrentMap().TRIGGER_LEFT.update(
getCurrentMap().TRIGGER_RIGHT.update((event.getAxisValue(MotionEvent.AXIS_RTRIGGER) > 0.5) || (event.getAxisValue(MotionEvent.AXIS_GAS) > 0.5)); (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) );
} }
} }

View File

@ -20,7 +20,7 @@ public class GamepadJoystick {
public static final int DIRECTION_SOUTH = 6; public static final int DIRECTION_SOUTH = 6;
public static final int DIRECTION_SOUTH_EAST = 7; public static final int DIRECTION_SOUTH_EAST = 7;
private float deadzone; private final InputDevice device;
private final int verticalAxis; private final int verticalAxis;
private final int horizontalAxis; private final int horizontalAxis;
@ -28,13 +28,9 @@ public class GamepadJoystick {
public GamepadJoystick(int horizontalAxis, int verticalAxis, InputDevice device){ public GamepadJoystick(int horizontalAxis, int verticalAxis, InputDevice device){
this.verticalAxis = verticalAxis; this.verticalAxis = verticalAxis;
this.horizontalAxis = horizontalAxis; 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){ 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 //to make it seem like there was no deadzone in the first place
double magnitude = getMagnitude(event); double magnitude = getMagnitude(event);
float deadzone = getDeadzone();
if (magnitude < deadzone) return 0; if (magnitude < deadzone) return 0;
return (float) ( (event.getAxisValue(axis) / magnitude) * ((magnitude - deadzone) / (1 - deadzone)) ); return (float) ( (event.getAxisValue(axis) / magnitude) * ((magnitude - deadzone) / (1 - deadzone)) );
@ -83,12 +80,20 @@ public class GamepadJoystick {
public int getHeightDirection(MotionEvent event){ 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; 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() { public float getDeadzone() {
return deadzone; try{
return Math.max(device.getMotionRange(horizontalAxis).getFlat() * 1.9f, 0.2f);
}catch (Exception e){
return 0.2f;
}
} }
} }

View File

@ -43,7 +43,7 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
private TextView mDeleteTextView; private TextView mDeleteTextView;
private TextView mCloneTextView; private TextView mCloneTextView;
private ControlButton editedButton; private final ControlButton editedButton;
public ActionPopupWindow(HandleView handleView, ControlButton button){ public ActionPopupWindow(HandleView handleView, ControlButton button){
super(handleView); super(handleView);
@ -145,6 +145,11 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
cloneData.properties.dynamicX = "0.5 * ${screen_width}"; cloneData.properties.dynamicX = "0.5 * ${screen_width}";
cloneData.properties.dynamicY = "0.5 * ${screen_height}"; cloneData.properties.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addDrawer(cloneData); ((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{ }else{
ControlData cloneData = new ControlData(editedButton.getProperties()); ControlData cloneData = new ControlData(editedButton.getProperties());
cloneData.dynamicX = "0.5 * ${screen_width}"; cloneData.dynamicX = "0.5 * ${screen_width}";

View File

@ -1,5 +1,9 @@
package net.kdt.pojavlaunch.utils; 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();
}
} }

View File

@ -79,6 +79,11 @@ public class CallbackBridge {
// sendData(JRE_TYPE_KEYCODE_CONTROL, keycode, Character.toString(keychar), Boolean.toString(isDown), modifiers); // 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) { public static void sendMouseKeycode(int button, int modifiers, boolean isDown) {
DEBUG_STRING.append("MouseKey=").append(button).append(", down=").append(isDown).append("\n"); 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"); // if (isGrabbing()) DEBUG_STRING.append("MouseGrabStrace: " + android.util.Log.getStackTraceString(new Throwable()) + "\n");

View File

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

View File

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

View File

@ -27,6 +27,11 @@
android:id="@+id/login_spinner_language" android:id="@+id/login_spinner_language"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -47,7 +52,7 @@
app:layout_constraintWidth_max="480dp" app:layout_constraintWidth_max="480dp"
android:layout_height="300dp" android:layout_height="300dp"
android:background="#272727" android:background="@drawable/menu_background"
android:translationZ="-1dp" android:translationZ="-1dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -53,8 +53,8 @@
android:layout_height="1dp" android:layout_height="1dp"
android:background="@android:color/darker_gray" android:background="@android:color/darker_gray"
android:ems="10" android:ems="10"
android:imeOptions="flagNoFullscreen|flagNoExtractUi|flagNoPersonalizedLearning|actionSend" android:imeOptions="flagNoFullscreen|flagNoExtractUi|flagNoPersonalizedLearning|actionDone"
android:inputType="textVisiblePassword" android:inputType="textFilter|textImeMultiLine|textAutoComplete|textAutoCorrect"
tools:ignore="TouchTargetSizeCheck" /> tools:ignore="TouchTargetSizeCheck" />
</net.kdt.pojavlaunch.customcontrols.ControlLayout> </net.kdt.pojavlaunch.customcontrols.ControlLayout>

View File

@ -160,6 +160,14 @@
<string name="control_customkey">Send custom keycode</string> <string name="control_customkey">Send custom keycode</string>
<string name="control_scaleup">Scale up</string> <string name="control_scaleup">Scale up</string>
<string name="control_scaledown">Scale down</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_more3"></string>
<string name="control_more4"></string> <string name="control_more4"></string>